Skip to content

Commit 2b8e05e

Browse files
Fix post-close docs and a dangling README reference
The cursor close() change that preserves rowcount/lastrowid (matching stdlib) left several docs stale: the sync and async close() method docstrings and a README bullet still said close() scrubs rowcount / lastrowid, and the _ExecuteManyAccumulator.apply() comment still listed _rowcount among the cleared fields. Correct all of them to state close() clears only the result-set surface (description / rows) and preserves rowcount / lastrowid; description remains the one documented divergence from stdlib. Also drop a dangling internal issues/ path that should not ship in the README. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent d43314b commit 2b8e05e

3 files changed

Lines changed: 30 additions & 30 deletions

File tree

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -188,10 +188,11 @@ borrowed from one.
188188
the prior INSERT's rowid sticky across executemany. The driver
189189
diverges deliberately (which row's rowid is "the" rowid for a batch
190190
of N inserts is ambiguous).
191-
- **`Cursor.close()` scrubs `description` / `lastrowid` / `_rows`.**
192-
stdlib preserves `description` and `lastrowid` post-close; dqlite
193-
scrubs to `None` to enforce the closed-cursor "no operation
194-
performed" surface.
191+
- **`Cursor.close()` clears `description` / `_rows` but preserves
192+
`rowcount` / `lastrowid`.** Matches stdlib for `rowcount`/`lastrowid`
193+
(both readable post-close); diverges only on `description`, which
194+
stdlib leaves populated whereas dqlite returns `None` (a closed
195+
cursor has no fetchable result set to describe).
195196
- **`Cursor.lastrowid` returns `None` after a fresh CREATE TABLE.**
196197
stdlib `sqlite3` returns `0` for a never-INSERTed cursor; dqlite
197198
returns `None`. After the first INSERT/REPLACE both drivers agree
@@ -217,9 +218,8 @@ borrowed from one.
217218
``check_same_thread=False`` opt-in behaviour. Default
218219
``check_same_thread=True`` enforces strict per-thread; methods
219220
called from a foreign thread on a default-mode Connection raise
220-
``ProgrammingError``. Tier 3 (cursor sharing) is tracked as
221-
future work in
222-
``issues/dbapi-threadsafety-tier-3-cursor-sharing-stdlib-parity.md``.
221+
``ProgrammingError``. Tier 3 (sharing a single cursor across threads)
222+
is not supported.
223223
- **No `executescript` / `create_function` / `create_aggregate` /
224224
`create_window_function` / `iterdump` / `backup` / `set_authorizer`
225225
/ `serialize` / `blobopen`.** stdlib-specific APIs that have no

src/dqlitedbapi/aio/cursor.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1318,10 +1318,11 @@ def close(self) -> None:
13181318
immediate ``NotSupportedError`` rather than a discarded
13191319
coroutine).
13201320
1321-
Scrubs ``description`` / ``rowcount`` / ``lastrowid`` /
1322-
``_rows`` / ``_row_index`` symmetrically with the sync sibling
1323-
``Cursor.close`` (see that docstring for the full
1324-
"post-close state" rationale).
1321+
Clears the result-set surface ``description`` / ``_rows`` /
1322+
``_row_index`` but PRESERVES ``rowcount`` / ``lastrowid``
1323+
(readable after close, matching stdlib ``sqlite3.Cursor``),
1324+
symmetrically with the sync sibling ``Cursor.close`` (see that
1325+
docstring for the full "post-close state" rationale).
13251326
13261327
**``arraysize`` is deliberately NOT scrubbed**: it is a
13271328
caller-set configuration *hint* (PEP 249 §6.1.2 default ``1``;

src/dqlitedbapi/cursor.py

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1202,11 +1202,11 @@ def apply(self, cursor: _ExecuteManyCursor) -> None:
12021202
otherwise.
12031203
12041204
No-op if the cursor has been closed concurrently — the async
1205-
``close()`` contract scrubs ``_rows``/``_description``/
1206-
``_rowcount``, and re-populating those fields here would
1207-
visibly un-close the result set for any attribute-level
1208-
caller. Sync flavour is immune via the outer threading lock;
1209-
this guard pins the async flavour. The per-iteration guard in
1205+
``close()`` contract clears ``_rows``/``_description``, and
1206+
re-populating those fields here would visibly un-close the
1207+
result set for any attribute-level caller. Sync flavour is
1208+
immune via the outer threading lock; this guard pins the async
1209+
flavour. The per-iteration guard in
12101210
async ``executemany`` catches the racy close between iterations;
12111211
this guard catches a close that lands after the loop exits
12121212
but before ``apply()`` writes state back.
@@ -2715,20 +2715,19 @@ def close(self) -> None:
27152715
thread-check ``ProgrammingError``. Matches stdlib
27162716
``sqlite3.Cursor.close`` — close is always safe to call.
27172717
2718-
**Divergence from stdlib sqlite3 on post-close attribute
2719-
state**: this implementation scrubs ``description`` /
2720-
``rowcount`` / ``lastrowid`` / ``_rows`` / ``_row_index``
2721-
for a consistent "no operation performed" surface (per
2722-
the project rationale: avoid stale-state reads on a closed
2723-
cursor). Stdlib ``sqlite3.Cursor.close()`` leaves
2724-
``description`` populated (the last query's tuple-of-7-tuples)
2725-
and leaves ``lastrowid`` at its prior value; only ``rowcount``
2726-
is reset to ``-1`` there. Cross-driver code that introspects
2727-
``cur.description`` AFTER ``close()`` (e.g. relying on
2728-
context-manager exit to close before reading metadata) sees
2729-
``None`` here vs the populated tuple on stdlib. PEP 249 is
2730-
silent on post-close attribute state; the scrub-for-consistency
2731-
choice is deliberate and documented.
2718+
**Post-close attribute state**: ``close()`` clears the
2719+
result-set surface — ``description`` / ``_rows`` /
2720+
``_row_index`` — because a closed cursor cannot serve a result
2721+
set, but PRESERVES ``rowcount`` and ``lastrowid``, matching
2722+
stdlib ``sqlite3.Cursor``, which leaves both readable after
2723+
``close()``. Reading ``cur.lastrowid`` / ``cur.rowcount`` after
2724+
``close()`` returns the last operation's values — load-bearing
2725+
for consumers such as SQLAlchemy's Result layer, which closes
2726+
the cursor and then reads ``cursor.lastrowid`` lazily.
2727+
``description`` is the one divergence: stdlib leaves the last
2728+
query's tuple-of-7-tuples populated, whereas this driver returns
2729+
``None`` post-close (a closed cursor has no fetchable result set
2730+
to describe). PEP 249 is silent on post-close attribute state.
27322731
27332732
**``arraysize`` is deliberately NOT scrubbed**: it is a
27342733
caller-set configuration *hint* (PEP 249 §6.1.2 default ``1``;

0 commit comments

Comments
 (0)