]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-135386: Fix "unable to open database file" errors on readonly DB (GH-135566)
authorGeneral_K1ng <generak1ng0@gmail.com>
Fri, 22 Aug 2025 11:11:59 +0000 (19:11 +0800)
committerGitHub <noreply@github.com>
Fri, 22 Aug 2025 11:11:59 +0000 (14:11 +0300)
Add immutable=1 flag for read-only SQLite access to avoid WAL/SHM errors on readonly DB.

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Lib/dbm/sqlite3.py
Lib/test/test_dbm_sqlite3.py
Misc/NEWS.d/next/Library/2025-06-16-15-00-13.gh-issue-135386.lNrxLc.rst [new file with mode: 0644]

index b296a1bcd1bbfa4b162259535d699ea8ed697cc0..c8ee6f184b365bdbd1c0781ebfbc5e138a2640a2 100644 (file)
@@ -60,18 +60,22 @@ class _Database(MutableMapping):
         # We use the URI format when opening the database.
         uri = _normalize_uri(path)
         uri = f"{uri}?mode={flag}"
+        if flag == "ro":
+            # Add immutable=1 to allow read-only SQLite access even if wal/shm missing
+            uri += "&immutable=1"
 
         try:
             self._cx = sqlite3.connect(uri, autocommit=True, uri=True)
         except sqlite3.Error as exc:
             raise error(str(exc))
 
-        # This is an optimization only; it's ok if it fails.
-        with suppress(sqlite3.OperationalError):
-            self._cx.execute("PRAGMA journal_mode = wal")
+        if flag != "ro":
+            # This is an optimization only; it's ok if it fails.
+            with suppress(sqlite3.OperationalError):
+                self._cx.execute("PRAGMA journal_mode = wal")
 
-        if flag == "rwc":
-            self._execute(BUILD_TABLE)
+            if flag == "rwc":
+                self._execute(BUILD_TABLE)
 
     def _execute(self, *args, **kwargs):
         if not self._cx:
index 9216da8a63f95751780b57c4b3e1ee7a9cec1aa4..15826f51c54180329e8e7fd23678925aab57d47d 100644 (file)
@@ -1,3 +1,5 @@
+import os
+import stat
 import sys
 import unittest
 from contextlib import closing
@@ -90,6 +92,49 @@ class ReadOnly(_SQLiteDbmTests):
         self.assertEqual([k for k in self.db], [b"key1", b"key2"])
 
 
+class ReadOnlyFilesystem(unittest.TestCase):
+
+    def setUp(self):
+        self.test_dir = os_helper.TESTFN
+        self.addCleanup(os_helper.rmtree, self.test_dir)
+        os.mkdir(self.test_dir)
+        self.db_path = os.path.join(self.test_dir, "test.db")
+
+        db = dbm_sqlite3.open(self.db_path, "c")
+        db[b"key"] = b"value"
+        db.close()
+
+    def test_readonly_file_read(self):
+        os.chmod(self.db_path, stat.S_IREAD)
+        with dbm_sqlite3.open(self.db_path, "r") as db:
+            self.assertEqual(db[b"key"], b"value")
+
+    def test_readonly_file_write(self):
+        os.chmod(self.db_path, stat.S_IREAD)
+        with dbm_sqlite3.open(self.db_path, "w") as db:
+            with self.assertRaises(dbm_sqlite3.error):
+                db[b"newkey"] = b"newvalue"
+
+    def test_readonly_dir_read(self):
+        os.chmod(self.test_dir, stat.S_IREAD | stat.S_IEXEC)
+        with dbm_sqlite3.open(self.db_path, "r") as db:
+            self.assertEqual(db[b"key"], b"value")
+
+    def test_readonly_dir_write(self):
+        os.chmod(self.test_dir, stat.S_IREAD | stat.S_IEXEC)
+        with dbm_sqlite3.open(self.db_path, "w") as db:
+            try:
+                db[b"newkey"] = b"newvalue"
+                modified = True  # on Windows and macOS
+            except dbm_sqlite3.error:
+                modified = False
+        with dbm_sqlite3.open(self.db_path, "r") as db:
+            if modified:
+                self.assertEqual(db[b"newkey"], b"newvalue")
+            else:
+                self.assertNotIn(b"newkey", db)
+
+
 class ReadWrite(_SQLiteDbmTests):
 
     def setUp(self):
diff --git a/Misc/NEWS.d/next/Library/2025-06-16-15-00-13.gh-issue-135386.lNrxLc.rst b/Misc/NEWS.d/next/Library/2025-06-16-15-00-13.gh-issue-135386.lNrxLc.rst
new file mode 100644 (file)
index 0000000..dbf1f45
--- /dev/null
@@ -0,0 +1,2 @@
+Fix opening a :mod:`dbm.sqlite3` database for reading from read-only file
+or directory.