-
-
Notifications
You must be signed in to change notification settings - Fork 30.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SQLite rowcount is corrupted when combining UPDATE RETURNING w/ id IN (?) #101117
Comments
This is reproducible with the Windows builds for 3.10.9, 3.10.8, 3.10.7, 3.10.6. |
Coincidentally the only sqlite related fix in 3.10.6 in the changelog is for #93421. |
A UPDATE ... RETURNING query returns resulting rows. Example use: query = "UPDATE some_table SET value='v2' WHERE id IN (?) RETURNING id"
params = (1,)
for row in cursor.execute(query, params):
print("row", row, "updated") The underlying SQLite library will only update the row count for every step that returns a row. In your example, you do not fetch all resulting rows; the UPDATE ... RETURNING statement is still running when you execute the SELECT query, effectively aborting the UPDATE ... RETURNING statement. |
Just as a last note for possible later reference should the circumstances around this change, I would note that one confounder I encountered was that on Azure Pipelines, we use 3.10.9 with the unfixed larger test case that is failing in 3.10.6 and above on Windows, and it passes every time. What does this mean? I do not know. |
Hm, yes that is a little bit weird. I'm reopening this until I've found time to investigate it again. |
Sorry 'bout the delay; I got lost in other issues. The issue here, is that you're executing a query that returns row, but you do not let that query complete (you do not collect the resulting rows, as expected by an UPDATE...RETURNING query). The behaviour SQLite C API AFAICS, this is misuse of the API: if you supply an UPDATE...RETURNING query, make sure you run it to completion before checking the row count. Perhaps a docs update could make this clearer. |
The SQLite C API sqlite3_changes() can only be relied upon when the statment has been run to completion.
Some more insightAlso, in earlier Python versions, the cursor object would pre-fetch a single row during execute; since 3.10 (?), rows are fetched lazily. The
Note the wording (emphasis by me): the most recently completed [...] statement What now?AFAIK, we now follow the rules of this particular SQLite C API, and I would prefer it to stay that way. It is unfortunate that 3.10 changed behaviour in a patch release. That is entirely my fault, and it shows we cannot always rely on our test suites. It also displays the dangers of slightly altering the semantics of existing API. Unfortunately, 3.10 is only accepting security fixes, so 3.10 stays how it is. For 3.11 and 3.12, I prefer to improve the docs only. Reverting gh-93421 is a backwards compatible change, and so is reverting the even older change of lazily fetching resulting rows. IMO, the current behaviour encourages good SQL and SQLite programming practise: if you run a query that returns resulting rows, make sure you collect them. |
The SQLite C API sqlite3_changes() can only be relied upon when the current active statement has been run to completion.
…ocs (python#104287) The SQLite C API sqlite3_changes() can only be relied upon when the current active statement has been run to completion.
The docs have been updated, hopefully to the better:
As explained here on the issue, and on the PR, I prefer to resolve this with docs updates only. The current implementation heeds the contract given by the SQLite C API. I do not think changing the semantics and behaviour of the If the current docs are still unclear; please let us know what is wrong and how to improve them. |
* main: (27 commits) pythongh-87849: fix SEND specialization family definition (pythonGH-104268) pythongh-101819: Adapt _io.IOBase.seek and _io.IOBase.truncate to Argument Clinic (python#104384) pythongh-101819: Adapt _io._Buffered* methods to Argument Clinic (python#104367) pythongh-101819: Refactor `_io` futher in preparation for module isolation (python#104369) pythongh-101819: Adapt _io.TextIOBase methods to Argument Clinic (python#104383) pythongh-101117: Improve accuracy of sqlite3.Cursor.rowcount docs (python#104287) pythonGH-92184: Convert os.altsep to '/' in filenames when creating ZipInfo objects (python#92185) pythongh-104357: fix inlined comprehensions that close over iteration var (python#104368) pythonGH-90208: Suppress OSError exceptions from `pathlib.Path.glob()` (pythonGH-104141) pythonGH-102181: Improve specialization stats for SEND (pythonGH-102182) pythongh-103000: Optimise `dataclasses.asdict` for the common case (python#104364) pythongh-103538: Remove unused TK_AQUA code (pythonGH-103539) pythonGH-87695: Fix OSError from `pathlib.Path.glob()` (pythonGH-104292) pythongh-104263: Rely on Py_NAN and introduce Py_INFINITY (pythonGH-104202) pythongh-104010: Separate and improve docs for `typing.get_origin` and `typing.get_args` (python#104013) pythongh-101819: Adapt _io._BufferedIOBase_Type methods to Argument Clinic (python#104355) pythongh-103960: Dark mode: invert image brightness (python#103983) pythongh-104252: Immortalize Py_EMPTY_KEYS (pythongh-104253) pythongh-101819: Clean up _io windows console io after pythongh-104197 (python#104354) pythongh-101819: Harden _io init (python#104352) ...
* main: pythongh-87849: fix SEND specialization family definition (pythonGH-104268) pythongh-101819: Adapt _io.IOBase.seek and _io.IOBase.truncate to Argument Clinic (python#104384) pythongh-101819: Adapt _io._Buffered* methods to Argument Clinic (python#104367) pythongh-101819: Refactor `_io` futher in preparation for module isolation (python#104369) pythongh-101819: Adapt _io.TextIOBase methods to Argument Clinic (python#104383) pythongh-101117: Improve accuracy of sqlite3.Cursor.rowcount docs (python#104287) pythonGH-92184: Convert os.altsep to '/' in filenames when creating ZipInfo objects (python#92185) pythongh-104357: fix inlined comprehensions that close over iteration var (python#104368) pythonGH-90208: Suppress OSError exceptions from `pathlib.Path.glob()` (pythonGH-104141)
Bug report
The rowcount from an sqlite UPDATE RETURNING query with WHERE id IN (?) used (but not == ?) will be 0.
The reproduction case from #93421 runs successfully. The modified reproduction case below errors with:
Reproduction case.
Your environment
This is the 3.10.9 windows install from python.org on my computer. My coworker installed the version on his Windows computer and could reproduce it as well.
My coworker has also had it happen on MacOS with 3.10.8
3.2 GHz 8-Core Intel Xeon W
Linked PRs
The text was updated successfully, but these errors were encountered: