Skip to content
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

Fixed #24018 -- Allowed setting pragma options on SQLite. #14824

Merged
merged 1 commit into from
Feb 16, 2024
Merged

Fixed #24018 -- Allowed setting pragma options on SQLite. #14824

merged 1 commit into from
Feb 16, 2024

Conversation

linville
Copy link
Contributor

@linville linville commented Sep 1, 2021

Adds support to the SQLite backend for the init_command option so user can set PRAGMA options when setting up the connection.

SQLite doesn't natively support the init_command keyword (such as MySQL does), the command is just run right after the connection is established.

Ticket #24018 in Trac

@github-actions
Copy link

github-actions bot commented Sep 1, 2021

Hello @linville! Thank you for your contribution 💪

As it's your first contribution be sure to check out the patch review checklist.

If you're fixing a ticket from Trac make sure to set the "Has patch" flag and include a link to this PR in the ticket!

If you have any design or process questions then you can ask in the Django forum.

Welcome aboard ⛵️!

Copy link
Member

@felixxm felixxm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@linville Thanks for the patch 👍 I can see one issue with this approach, we don't inform users when sth goes wrong.

tests/backends/sqlite/tests.py Outdated Show resolved Hide resolved
tests/backends/sqlite/tests.py Outdated Show resolved Hide resolved
tests/backends/sqlite/tests.py Outdated Show resolved Hide resolved
docs/ref/databases.txt Show resolved Hide resolved
docs/ref/databases.txt Outdated Show resolved Hide resolved
docs/ref/databases.txt Outdated Show resolved Hide resolved
docs/ref/databases.txt Show resolved Hide resolved
docs/ref/databases.txt Outdated Show resolved Hide resolved
docs/ref/databases.txt Outdated Show resolved Hide resolved
@linville linville marked this pull request as draft September 3, 2021 02:04
@linville
Copy link
Contributor Author

linville commented Sep 3, 2021

@linville Thanks for the patch 👍 I can see one issue with this approach, we don't inform users when sth goes wrong.

Thanks for reviewing! Could you expand on "when sth goes wrong", not sure I'm following? Would this be if the user uses invalid pragma/syntax in the init_command?

@felixxm
Copy link
Member

felixxm commented Sep 3, 2021

Thanks for reviewing! Could you expand on "when sth goes wrong", not sure I'm following? Would this be if the user uses invalid pragma/syntax in the init_command?

As in the ticket description, PRAGMA journal_mode=wal; return wal on success and delete/memory when it fails. We don't validate the output so a user is not aware that journal_mode is unchanged. Maybe that's fine.

See also Carlton's comment.

@linville
Copy link
Contributor Author

linville commented Sep 8, 2021

As in the ticket description, PRAGMA journal_mode=wal; return wal on success and delete/memory when it fails. We don't validate the output so a user is not aware that journal_mode is unchanged. Maybe that's fine.

Looking at the MySQL backend behavior for init_command, if something goes wrong the django.db.utils.ProgrammingError is raised with the SQL statement which would be useful to alert the user to errors. Having the sqlite backend implementation of init_command behave similar to the MySQL one would certainly be reasonable (though I suppose the opposite could be argued since it isn't Django or even the MySQL python module doing the validation but rather MySQL itself).

Thinking about approaches for this: whether a value is returned from a pragma statement varies on the pragma, some will return a value (e.g.: journal_mode) and some will not (e.g.: cache_size). It looks like an implementation of this would involve running two cursor executes for each pragma being set by the init_command: first execute the pragma setting statement and then then running another cursor execute to get the pragma's value and check that it matches what was assigned (this code would have to split multiple pragma set statements in the init_command and also extract the pragma names, but the syntax is simple so this shouldn't be too complex).

If we'd like to do validation, I'd be happy to take a shot at an implementation of the above approach if it seems reasonable.

@linville linville marked this pull request as ready for review September 16, 2021 19:48
@bcail
Copy link
Contributor

bcail commented Nov 23, 2022

@linville @felixxm Is there anything I can do to help move this forward?

@linville linville marked this pull request as draft August 17, 2023 21:08
@linville linville marked this pull request as ready for review August 18, 2023 01:10
@bcail
Copy link
Contributor

bcail commented Feb 7, 2024

I'm not sure we'd need to validate the result of the PRAGMA command. We don't verify the PRAGMA statements that django always runs. The user could also manually verify the value if desired.

@anze3db
Copy link
Sponsor Contributor

anze3db commented Feb 8, 2024

I think validating every statement in the init_command isn't practical because we don't control what the users actually do. The way that the current PR is implemented there is nothing stopping users from writing CREATE TABLE/INSERT/UPDATE/... statements in the init_command.

If we do want validation, then I think a better approach would be to expose the pragma settings that we want to support and validate through DATABASE OPTIONS e.g. DATABASE["default"]["OPTIONS"]["journal_mode"].

To me personally either approach works, I'd just really like to have this feature land in 5.1! Happy to help out in any way I can to get this done.

@linville linville marked this pull request as draft February 8, 2024 22:20
@linville linville marked this pull request as ready for review February 8, 2024 23:18
docs/ref/databases.txt Outdated Show resolved Hide resolved
docs/ref/databases.txt Outdated Show resolved Hide resolved
docs/ref/databases.txt Outdated Show resolved Hide resolved
docs/ref/databases.txt Outdated Show resolved Hide resolved
docs/ref/databases.txt Outdated Show resolved Hide resolved
docs/releases/5.1.txt Outdated Show resolved Hide resolved
@felixxm felixxm changed the title Fixed #24018 -- SQLite backend supports the init_command option Fixed #24018 -- Allow setting pragma options on SQLite. Feb 14, 2024
@felixxm felixxm changed the title Fixed #24018 -- Allow setting pragma options on SQLite. Fixed #24018 -- Allowed setting pragma options on SQLite. Feb 14, 2024
@felixxm
Copy link
Member

felixxm commented Feb 14, 2024

@linville Thanks for updates 👍 I left small suggestions.

@linville linville marked this pull request as draft February 15, 2024 13:11
@linville linville marked this pull request as ready for review February 15, 2024 14:54
@gcollazo
Copy link

Great work thanks!

Would it be possible/make sense to set or recommend (in docs) a "sensible default configuration" for using SQLite in the context of a web app?

Ruby on Rails being very opinionated, decided to set some defaults. To me this makes a lot sense since this is mostly the configuration I'm using in my web projects.

The only thing missing is setting the busy_timeout but I think they have implemented a connection retry and backoff mechanism to deal with that at the framework layer instead of at the SQLite layer.

DEFAULT_PRAGMAS = {
    "foreign_keys"        => true,
    "journal_mode"        => :wal,
    "synchronous"         => :normal,
    "mmap_size"           => 134217728, # 128 megabytes
    "journal_size_limit"  => 67108864, # 64 megabytes
    "cache_size"          => 2000
}

Here's a link to the RoR settings for SQLite and here's a great blog post that helps understand the configuration and performs some benchmarks.

@felixxm
Copy link
Member

felixxm commented Feb 15, 2024

Would it be possible/make sense to set or recommend (in docs) a "sensible default configuration" for using SQLite in the context of a web app?

I don't think so. If we decided we needed more options we would set them automatically, as we already do:

conn.execute("PRAGMA foreign_keys = ON")
# The macOS bundled SQLite defaults legacy_alter_table ON, which
# prevents atomic table renames.
conn.execute("PRAGMA legacy_alter_table = OFF")

You can start a discussion on the Django Forum if you strongly believe that Django should set more options by default.

@linville
Copy link
Contributor Author

@linville Thanks for updates 👍 I left small suggestions.

Thank you for the review! I believe (hope!) I've got the requested changes incorporated now.

@felixxm felixxm self-assigned this Feb 15, 2024
"ENGINE": "django.db.backends.sqlite3",
"NAME": ":memory:",
"OPTIONS": {
"init_command": "PRAGMA synchronous=3;",
Copy link
Member

@felixxm felixxm Feb 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that it won't work for multiple PRAGMA statements:

sqlite3.Warning: You can only execute one statement at a time.

I think we should change it to the list. It's probably better for consistency with MySQL options to split this on ;.

@felixxm
Copy link
Member

felixxm commented Feb 16, 2024

@linville Thanks 👍 Welcome aboard ⛵

@felixxm felixxm merged commit 7a05b8a into django:main Feb 16, 2024
35 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants