db.query() now executes its SQL immediately

Previously query() was a generator function, so nothing - including
the SQL itself - ran until the result was first iterated. A write
statement passed to query() silently did nothing, and SQL errors
surfaced far from the call site. The SQL now executes as soon as
query() is called, while rows are still fetched lazily during
iteration.

Statements that return no rows now raise a ValueError directing
callers to execute() instead. As a side effect, statements like
INSERT ... RETURNING now work naturally with query().

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UnLnhsH25Nnv7LHhekUfPd
This commit is contained in:
Claude 2026-07-04 18:24:52 +00:00
commit 45af93d463
No known key found for this signature in database
3 changed files with 49 additions and 2 deletions

View file

@ -593,14 +593,28 @@ class Database:
"""
Execute ``sql`` and return an iterable of dictionaries representing each row.
The SQL is executed as soon as this method is called - the resulting rows
are then fetched lazily as the returned iterable is iterated over.
:param sql: SQL query to execute
:param params: Parameters to use in that query - an iterable for ``where id = ?``
parameters, or a dictionary for ``where id = :id``
:raises ValueError: if the SQL statement does not return rows - use
:meth:`execute` for those statements instead
"""
cursor = self.execute(sql, params or tuple())
if cursor.description is None:
raise ValueError(
"query() can only be used with SQL that returns rows - "
"use execute() for other statements"
)
keys = [d[0] for d in cursor.description]
for row in cursor:
yield dict(zip(keys, row))
def rows() -> Generator[dict, None, None]:
for row in cursor:
yield dict(zip(keys, row))
return rows()
def execute(
self, sql: str, parameters: Optional[Union[Sequence, Dict[str, Any]]] = None