gh-135386: Fix "unable to open database file" errors on readonly DB (GH-135566)

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>
This commit is contained in:
General_K1ng 2025-08-22 19:11:59 +08:00 committed by GitHub
parent f27af8ba8e
commit c0ae92b7c0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 56 additions and 5 deletions

View 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:

View 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):

View file

@ -0,0 +1,2 @@
Fix opening a :mod:`dbm.sqlite3` database for reading from read-only file
or directory.