Skip to content

Commit a411089

Browse files
GeneralK1ngserhiy-storchaka
authored andcommitted
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. (cherry picked from commit c0ae92b) Co-authored-by: General_K1ng <[email protected]> Co-authored-by: Serhiy Storchaka <[email protected]>
1 parent 57c4601 commit a411089

File tree

3 files changed

+56
-5
lines changed

3 files changed

+56
-5
lines changed

Lib/dbm/sqlite3.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,18 +59,22 @@ def __init__(self, path, /, *, flag, mode):
5959
# We use the URI format when opening the database.
6060
uri = _normalize_uri(path)
6161
uri = f"{uri}?mode={flag}"
62+
if flag == "ro":
63+
# Add immutable=1 to allow read-only SQLite access even if wal/shm missing
64+
uri += "&immutable=1"
6265

6366
try:
6467
self._cx = sqlite3.connect(uri, autocommit=True, uri=True)
6568
except sqlite3.Error as exc:
6669
raise error(str(exc))
6770

68-
# This is an optimization only; it's ok if it fails.
69-
with suppress(sqlite3.OperationalError):
70-
self._cx.execute("PRAGMA journal_mode = wal")
71+
if flag != "ro":
72+
# This is an optimization only; it's ok if it fails.
73+
with suppress(sqlite3.OperationalError):
74+
self._cx.execute("PRAGMA journal_mode = wal")
7175

72-
if flag == "rwc":
73-
self._execute(BUILD_TABLE)
76+
if flag == "rwc":
77+
self._execute(BUILD_TABLE)
7478

7579
def _execute(self, *args, **kwargs):
7680
if not self._cx:

Lib/test/test_dbm_sqlite3.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import os
2+
import stat
13
import sys
24
import unittest
35
from contextlib import closing
@@ -90,6 +92,49 @@ def test_readonly_iter(self):
9092
self.assertEqual([k for k in self.db], [b"key1", b"key2"])
9193

9294

95+
class ReadOnlyFilesystem(unittest.TestCase):
96+
97+
def setUp(self):
98+
self.test_dir = os_helper.TESTFN
99+
self.addCleanup(os_helper.rmtree, self.test_dir)
100+
os.mkdir(self.test_dir)
101+
self.db_path = os.path.join(self.test_dir, "test.db")
102+
103+
db = dbm_sqlite3.open(self.db_path, "c")
104+
db[b"key"] = b"value"
105+
db.close()
106+
107+
def test_readonly_file_read(self):
108+
os.chmod(self.db_path, stat.S_IREAD)
109+
with dbm_sqlite3.open(self.db_path, "r") as db:
110+
self.assertEqual(db[b"key"], b"value")
111+
112+
def test_readonly_file_write(self):
113+
os.chmod(self.db_path, stat.S_IREAD)
114+
with dbm_sqlite3.open(self.db_path, "w") as db:
115+
with self.assertRaises(dbm_sqlite3.error):
116+
db[b"newkey"] = b"newvalue"
117+
118+
def test_readonly_dir_read(self):
119+
os.chmod(self.test_dir, stat.S_IREAD | stat.S_IEXEC)
120+
with dbm_sqlite3.open(self.db_path, "r") as db:
121+
self.assertEqual(db[b"key"], b"value")
122+
123+
def test_readonly_dir_write(self):
124+
os.chmod(self.test_dir, stat.S_IREAD | stat.S_IEXEC)
125+
with dbm_sqlite3.open(self.db_path, "w") as db:
126+
try:
127+
db[b"newkey"] = b"newvalue"
128+
modified = True # on Windows and macOS
129+
except dbm_sqlite3.error:
130+
modified = False
131+
with dbm_sqlite3.open(self.db_path, "r") as db:
132+
if modified:
133+
self.assertEqual(db[b"newkey"], b"newvalue")
134+
else:
135+
self.assertNotIn(b"newkey", db)
136+
137+
93138
class ReadWrite(_SQLiteDbmTests):
94139

95140
def setUp(self):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix opening a :mod:`dbm.sqlite3` database for reading from read-only file
2+
or directory.

0 commit comments

Comments
 (0)