-
-
Notifications
You must be signed in to change notification settings - Fork 31.3k
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
bpo-36859: Use sqlite3_stmt_readonly API when possible to determine if statement is DML.K #13216
Conversation
Hello, and thanks for your contribution! I'm a bot set up to make sure that the project can legally accept your contribution by verifying you have signed the PSF contributor agreement (CLA). Our records indicate we have not received your CLA. For legal reasons we need you to sign this before we can look at your contribution. Please follow the steps outlined in the CPython devguide to rectify this issue. If you have recently signed the CLA, please wait at least one business day You can check yourself to see if the CLA has been received. Thanks again for your contribution, we look forward to reviewing it! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a great improvement, thank you for your PR!
Misc/NEWS.d/next/Library/2019-05-09-09-54-00.bpo-36859.CuCkEd.rst
Outdated
Show resolved
Hide resolved
A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated. Once you have made the requested changes, please leave a comment on this pull request containing the phrase |
I have made the requested changes; please review again. |
Thanks for making the requested changes! @berkerpeksag: please review the changes made to this pull request. |
Oh thanks for pushing the markup fix, my bad! So what happens now? :) |
I'm testing it locally now. Hopefully, I'm going to merge it tonight :) |
Apologies for the new commit, but I was just thinking and realized that sqlite supports a limited subset of ALTER TABLE. So ALTER, plus VACUUM/ANALYZE/REINDEX have been added to the list. These are queries that may write to the database (so |
Good catch! We had a test for Also, looking at old commits and issues, it seems to me that we can move new tests into the existing test case cpython/Lib/sqlite3/test/transactions.py Line 170 in 137be34
What do you think? |
Also add additional assertion covering ALTER statements.
I agree, and have moved the tests into the transactions module. I also added an assertion covering the ALTER statement to an existing transactional DDL case. |
Hi, I have two worries. 1, Fallback when sqlite doesn't have 2, using BTW, |
How about this proposal. Don't change the determine logic of rc = conn.execute(
'with c(k, v) as (select key, value + 10 from kv) '
'update kv set value=(select v from c where k=kv.key)') We encourage the user to set Add a new parameter /* in file \Modules\_sqlite\cursor.c */
- if (self->statement->is_dml) {
+ if (self->statement->is_dml || count_row) {
self->rowcount += (long)sqlite3_changes(self->connection->db);
} else {
self->rowcount= -1L;
} The idea is to let user manipulate complex statement manually, in order to round the historical issues. |
As long as you aren't using a 9 year old sqlite, the behavior will be correct. The behavior is currently buggy.
That patch is a much worse implementation of what this patch fixes. That patch was not merged. The use of strncmp is sketchy, I agree, but please let's not throw the baby out with the bathwater. Currently there are some real issues with the logic. The crux of the issue is that the sqlite lib needs to enter a txn if the user issues a data modifying statement... except ddl is not included in this. My sense is that this patch is a real improvement. But is it perfect? No. Nice catch I somehow misread the version. |
Ultimately it's up to the core team. This feels cheesey to me, tho. Regarding isolation level: yes! For myself, I always always always set to none, because pysqlites implementation is so problematic. This patch isn't trying to change the existing behavior, though. Simply make it more reliable. Again,it's just a patch, it's up to the core devs to decide. I've already got a standalone pysqlite3 package and have merged these changes over there. |
I'm thinking about introducing a This is similar to what we do in email package: https://docs.python.org/3/library/email.policy.html#module-email.policy It would also help us to fix other quirks of sqlite3 module without breaking backwards compatibility. |
Rethinking the whole transaction handling is great, but obviously not something I had been trying to undertake. If such a plan moves forward then this patch is not needed. |
So thinking about this some more, and also looking at the interest and motivation for #10913 -- I do think there's sufficient interest in solving this particular bug, which I summarize as:
Ramifications: transaction controls may not reliably cover certain queries (CTEs, preceding comments), which also leads to the cursor not exposing the rowcount attribute. This patch certainly fixes this, and fixes it much better than the linked patch. Reasons why this patch is not perfect:
Options, as I see them:
|
I'm definitely going to merge this PR. The |
Feature added in sqlite 3.7.4, not .11.
It would be nice to remove sqlite3's warts by adding a new mode. I'm not strongly against this patch, because my proposal is not very beautiful. For this patch, issue35398's problem is more serious, some statements that begin with a comment may abort the program: >>> conn.execute('/* comment */ vacuum') # this will start a transaction
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
sqlite3.OperationalError: cannot VACUUM from within a transaction It seems we should detect leading comments like PR #10913. RHEL6 (Product retirement at November 30, 2020) is still alive, it's shipped with SQLite 3.6.x. How about emit a RuntimeWarning when importing sqlite3 module if |
Why don't you grep the pysqlite for ifdefs. You'll find that there are a number of differences that are dependant on the sqlite version. In the case of an old version the original behavior is retained, so I don't understand why raising a runtime error makes sense. Similarly, this patch is intended to do away with the necessity to parse comments. This patch fixes the issue you referenced. |
Search
The first two don't introduce different behaviors. The third one is documented clearly, and if run a code on old SQLite, the program will abort. While with this patch, please imagine this scene: the same program, under the same version Python, can get different results silently. If this happened, it's very hard to find where the bug is, the programmer may get insane. Even worse, no one noticed the difference at all, but polluted results may break things (at that time or in the future) like a specter. Moreover, IMO
With this patch, this statement can't be executed:
You still have to detect leading comments. |
Do you? I don't know. The error message is clear, the solution simple? |
These are all good points. I don't see a good way forward that is both backward-compatible and can maintain compatibility across potentially different libsqlite3 installs. Edge-cases with current implementation:
Edge-cases with this patch:
|
Due to the leading comment, this simple code will abort with this patch: import sqlite3
conn = sqlite3.connect(':memory:')
conn.execute('/* comment */ vacuum')
It's a troublesome issue. If must pick a solution, I vote for this order: IMO
Hard to decide for me, let the core developers choose. :) |
What's the status on this? |
After #24106, this PR can be greatly simplified. Its not for me to decide, but I think this is an improvement, and I'm in favour of merging it (after a rebase onto current master). |
Implements fix for https://bugs.python.org/issue36859
The stdlib sqlite3 module attempts to detect if a statement is data-modifying, which it currently does by stripping whitespace and looking at the start of the SQL string. There are numerous issues with this approach:
In sqlite 3.7.11, the
sqlite3_stmt_readonly
API was added which implements this logic correctly.pysqlite has weeeird quirks with how it handles DDL (create/drop), which the above api correctly identifies as writing to the db, but for our purposes should not be considered DML. Same for
begin exclusive
andbegin immediate
transaction controls.So this patch:
Sqlite docs for sqlite3_stmt_readonly.
https://bugs.python.org/issue36859