Skip to content

Commit 8f22802

Browse files
miss-islingtonGeneralK1ngserhiy-storchaka
authored
[3.13] gh-135386: Fix "unable to open database file" errors on readonly DB (GH-135566) (GH-138057)
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 a73515e commit 8f22802

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 test.support
35
import unittest
@@ -91,6 +93,49 @@ def test_readonly_iter(self):
9193
self.assertEqual([k for k in self.db], [b"key1", b"key2"])
9294

9395

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

96141
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)