Skip to content

Commit d29acdc

Browse files
Recognise VALUES and leading-paren forms as row-returning
_is_row_returning previously missed two valid top-level SQLite statements: ``VALUES (...)`` and ``(SELECT ...)``. Both produce result sets but took the DML branch, so the cursor called the non-query path and returned no rows. Add VALUES to the prefix tuple and strip a single leading ``(`` before matching so wrapped-select forms are classified correctly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 75a4f2b commit d29acdc

2 files changed

Lines changed: 15 additions & 4 deletions

File tree

src/dqlitedbapi/cursor.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,21 +144,25 @@ def _strip_leading_comments(sql: str) -> str:
144144
return s
145145

146146

147-
_ROW_RETURNING_PREFIXES = ("SELECT", "PRAGMA", "EXPLAIN", "WITH")
147+
_ROW_RETURNING_PREFIXES = ("SELECT", "VALUES", "PRAGMA", "EXPLAIN", "WITH")
148148

149149

150150
def _is_row_returning(sql: str) -> bool:
151151
"""Heuristic for "does this statement return a result set?"
152152
153153
Single source of truth for sync and async cursors.
154-
Matches leading SELECT/PRAGMA/EXPLAIN/WITH after stripping comments,
155-
and catches trailing/embedded RETURNING clauses on DML.
154+
Matches leading SELECT/VALUES/PRAGMA/EXPLAIN/WITH after stripping
155+
comments and a single leading ``(``, and catches trailing or
156+
embedded RETURNING clauses on DML.
157+
158+
``VALUES (...)`` and ``(SELECT ...)`` are valid top-level
159+
row-returning SQLite statements, so they take the query branch.
156160
157161
Note: ``WITH ... INSERT/UPDATE/DELETE`` (no RETURNING) will be
158162
misclassified as a query. This is a known limitation of a
159163
prefix-only check — a full SQL parser is out of scope.
160164
"""
161-
normalized = _strip_leading_comments(sql).upper()
165+
normalized = _strip_leading_comments(sql).upper().lstrip("(")
162166
if normalized.startswith(_ROW_RETURNING_PREFIXES):
163167
return True
164168
return " RETURNING " in normalized or normalized.endswith(" RETURNING")

tests/test_dbapi_hardening.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,13 @@ class TestIsRowReturning:
324324
"WITH cte AS (SELECT 1) SELECT * FROM cte",
325325
"INSERT INTO t VALUES (?) RETURNING id",
326326
"UPDATE t SET x = 1 WHERE id = ? RETURNING *",
327+
# VALUES (...) is a valid top-level row-returning SQLite statement.
328+
"VALUES (1), (2), (3)",
329+
"values (1, 'a')",
330+
" -- preamble\nVALUES (1)",
331+
# Leading paren on a row-returning form.
332+
"(SELECT 1)",
333+
"(SELECT a FROM t) UNION (SELECT b FROM u)",
327334
],
328335
)
329336
def test_detects_row_returning(self, sql: str) -> None:

0 commit comments

Comments
 (0)