From 7a05b8a2fac57e32a61726893d4601352c1d1c8d Mon Sep 17 00:00:00 2001 From: Aaron Linville Date: Thu, 17 Aug 2023 18:06:25 -0400 Subject: [PATCH] Fixed #24018 -- Allowed setting pragma options on SQLite. --- AUTHORS | 1 + django/db/backends/sqlite3/base.py | 6 ++++++ docs/ref/databases.txt | 24 ++++++++++++++++++++++++ docs/releases/5.1.txt | 7 +++++++ docs/spelling_wordlist | 1 + tests/backends/sqlite/tests.py | 23 +++++++++++++++++++++++ 6 files changed, 62 insertions(+) diff --git a/AUTHORS b/AUTHORS index 6fef75cf43..945a017cf8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -6,6 +6,7 @@ people who have submitted patches, reported bugs, added translations, helped answer newbie questions, and generally made Django that much better: Aaron Cannon + Aaron Linville Aaron Swartz Aaron T. Myers Abeer Upadhyay diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index 8e17ea3d44..c7cf947800 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -187,6 +187,9 @@ class DatabaseWrapper(BaseDatabaseWrapper): f"{allowed_transaction_modes}, or None." ) self.transaction_mode = transaction_mode.upper() if transaction_mode else None + + init_command = kwargs.pop("init_command", "") + self.init_commands = init_command.split(";") return kwargs def get_database_version(self): @@ -201,6 +204,9 @@ class DatabaseWrapper(BaseDatabaseWrapper): # The macOS bundled SQLite defaults legacy_alter_table ON, which # prevents atomic table renames. conn.execute("PRAGMA legacy_alter_table = OFF") + for init_command in self.init_commands: + if init_command := init_command.strip(): + conn.execute(init_command) return conn def create_cursor(self, name=None): diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index d853647730..1bc787671e 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -941,6 +941,30 @@ To enable the JSON1 extension you can follow the instruction on .. _JSON1 extension: https://www.sqlite.org/json1.html .. _the wiki page: https://code.djangoproject.com/wiki/JSON1Extension +.. _sqlite-init-command: + +Setting pragma options +---------------------- + +.. versionadded:: 5.1 + +`Pragma options`_ can be set upon connection by using the ``init_command`` in +the :setting:`OPTIONS` part of your database configuration in +:setting:`DATABASES`. The example below shows how to enable extra durability of +synchronous writes and change the ``cache_size``:: + + DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + # ... + "OPTIONS": { + "init_command": "PRAGMA synchronous=3; PRAGMA cache_size=2000;", + }, + } + } + +.. _Pragma options: https://www.sqlite.org/pragma.html + .. _oracle-notes: Oracle notes diff --git a/docs/releases/5.1.txt b/docs/releases/5.1.txt index 94c342e8a0..e288bab20c 100644 --- a/docs/releases/5.1.txt +++ b/docs/releases/5.1.txt @@ -146,6 +146,13 @@ CSRF * ... +Database backends +~~~~~~~~~~~~~~~~~ + +* ``"init_command"`` option is now supported in :setting:`OPTIONS` on SQLite + to allow specifying :ref:`pragma options ` to set upon + connection. + Decorators ~~~~~~~~~~ diff --git a/docs/spelling_wordlist b/docs/spelling_wordlist index 5828b24253..1044cd80eb 100644 --- a/docs/spelling_wordlist +++ b/docs/spelling_wordlist @@ -361,6 +361,7 @@ postfix postgis postgres postgresql +pragma pre precisions precomputation diff --git a/tests/backends/sqlite/tests.py b/tests/backends/sqlite/tests.py index 42fee432f9..91dd83f134 100644 --- a/tests/backends/sqlite/tests.py +++ b/tests/backends/sqlite/tests.py @@ -116,6 +116,29 @@ class Tests(TestCase): connection.check_database_version_supported() self.assertTrue(mocked_get_database_version.called) + def test_init_command(self): + settings_dict = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": ":memory:", + "OPTIONS": { + "init_command": "PRAGMA synchronous=3; PRAGMA cache_size=2000;", + }, + } + } + connections = ConnectionHandler(settings_dict) + connections["default"].ensure_connection() + try: + with connections["default"].cursor() as cursor: + cursor.execute("PRAGMA synchronous") + value = cursor.fetchone()[0] + self.assertEqual(value, 3) + cursor.execute("PRAGMA cache_size") + value = cursor.fetchone()[0] + self.assertEqual(value, 2000) + finally: + connections["default"].close() + @unittest.skipUnless(connection.vendor == "sqlite", "SQLite tests") @isolate_apps("backends")