mirror of
https://github.com/simonw/sqlite-utils.git
synced 2026-08-12 12:24:13 +02:00
Emit LIMIT -1 when offset is used without limit (#821)
* Emit LIMIT -1 when offset is used without limit, closes #816 SQLite requires a LIMIT clause to appear before OFFSET, so passing offset without limit generated invalid SQL such as: select * from "t" offset 2 which raised OperationalError: near "2": syntax error. A negative limit means "no upper bound" in SQLite, so "limit -1 offset N" returns all rows from position N onwards. Fixed in three places that build LIMIT/OFFSET SQL: - Queryable.rows_where() - also covers pks_and_rows_where() - Table.search_sql() - also covers search() - the "sqlite-utils rows" CLI command * Remove duplicate comments --------- Co-authored-by: ethanhawkes-gif <259455325+ethanhawkes-gif@users.noreply.github.com>
This commit is contained in:
parent
2d3c6b9a1e
commit
43d5d3331f
5 changed files with 33 additions and 0 deletions
|
|
@ -2478,6 +2478,8 @@ def rows(
|
|||
if limit:
|
||||
sql += f" limit {limit}"
|
||||
if offset:
|
||||
if not limit:
|
||||
sql += " limit -1"
|
||||
sql += f" offset {offset}"
|
||||
ctx.invoke(
|
||||
query,
|
||||
|
|
|
|||
|
|
@ -2064,6 +2064,10 @@ class Queryable:
|
|||
if limit is not None:
|
||||
sql += f" limit {limit}"
|
||||
if offset is not None:
|
||||
# SQLite requires a limit clause before offset - a negative limit
|
||||
# means "no upper bound", so offset works without an explicit limit
|
||||
if limit is None:
|
||||
sql += " limit -1"
|
||||
sql += f" offset {offset}"
|
||||
cursor = self.db.execute(sql, where_args or [])
|
||||
columns = dedupe_keys(c[0] for c in cursor.description)
|
||||
|
|
@ -3732,6 +3736,8 @@ class Table(Queryable):
|
|||
if limit is not None:
|
||||
limit_offset += f" limit {limit}"
|
||||
if offset is not None:
|
||||
if limit is None:
|
||||
limit_offset += " limit -1"
|
||||
limit_offset += f" offset {offset}"
|
||||
return sql.format(
|
||||
dbtable=quote_identifier(self.name),
|
||||
|
|
|
|||
|
|
@ -1183,6 +1183,11 @@ def test_query_memory_does_not_create_file(tmpdir):
|
|||
["-c", "name", "--limit", "1", "--offset", "1"],
|
||||
'[{"name": "Pancakes"}]',
|
||||
),
|
||||
# --offset without --limit
|
||||
(
|
||||
["-c", "name", "--offset", "1"],
|
||||
'[{"name": "Pancakes"}]',
|
||||
),
|
||||
# --where
|
||||
(
|
||||
["-c", "name", "--where", "id = 1"],
|
||||
|
|
|
|||
|
|
@ -112,6 +112,17 @@ def test_search_limit_offset(fresh_db):
|
|||
)
|
||||
|
||||
|
||||
def test_search_offset_without_limit(fresh_db):
|
||||
table = fresh_db["t"]
|
||||
table.insert_all(search_records)
|
||||
table.enable_fts(["text", "country"], fts_version="FTS4")
|
||||
assert [row["rowid"] for row in table.search("are", order_by="rowid")] == [1, 2]
|
||||
assert [
|
||||
row["rowid"] for row in table.search("are", offset=1, order_by="rowid")
|
||||
] == [2]
|
||||
assert table.search_sql(offset=1).strip().endswith("limit -1 offset 1")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fts_version", ("FTS4", "FTS5"))
|
||||
def test_search_where(fresh_db, fts_version):
|
||||
table = fresh_db["t"]
|
||||
|
|
|
|||
|
|
@ -59,6 +59,9 @@ def test_rows_where_order_by(where, order_by, expected_ids, fresh_db):
|
|||
(None, 3, [1, 2, 3]),
|
||||
(0, 3, [1, 2, 3]),
|
||||
(3, 3, [4, 5, 6]),
|
||||
# offset without limit should return every remaining row
|
||||
(97, None, [98, 99, 100]),
|
||||
(0, None, list(range(1, 101))),
|
||||
],
|
||||
)
|
||||
def test_rows_where_offset_limit(fresh_db, offset, limit, expected):
|
||||
|
|
@ -70,6 +73,12 @@ def test_rows_where_offset_limit(fresh_db, offset, limit, expected):
|
|||
]
|
||||
|
||||
|
||||
def test_pks_and_rows_where_offset_without_limit(fresh_db):
|
||||
table = fresh_db["rows"]
|
||||
table.insert_all([{"id": id} for id in range(1, 6)], pk="id")
|
||||
assert [pk for pk, _ in table.pks_and_rows_where(offset=3, order_by="id")] == [4, 5]
|
||||
|
||||
|
||||
def test_pks_and_rows_where_rowid(fresh_db):
|
||||
table = fresh_db["rowid_table"]
|
||||
table.insert_all({"number": i + 10} for i in range(3))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue