mirror of
https://github.com/python/cpython.git
synced 2025-11-24 12:20:42 +00:00
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:
parent
f27af8ba8e
commit
c0ae92b7c0
3 changed files with 56 additions and 5 deletions
|
|
@ -60,18 +60,22 @@ class _Database(MutableMapping):
|
||||||
# We use the URI format when opening the database.
|
# We use the URI format when opening the database.
|
||||||
uri = _normalize_uri(path)
|
uri = _normalize_uri(path)
|
||||||
uri = f"{uri}?mode={flag}"
|
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:
|
try:
|
||||||
self._cx = sqlite3.connect(uri, autocommit=True, uri=True)
|
self._cx = sqlite3.connect(uri, autocommit=True, uri=True)
|
||||||
except sqlite3.Error as exc:
|
except sqlite3.Error as exc:
|
||||||
raise error(str(exc))
|
raise error(str(exc))
|
||||||
|
|
||||||
# This is an optimization only; it's ok if it fails.
|
if flag != "ro":
|
||||||
with suppress(sqlite3.OperationalError):
|
# This is an optimization only; it's ok if it fails.
|
||||||
self._cx.execute("PRAGMA journal_mode = wal")
|
with suppress(sqlite3.OperationalError):
|
||||||
|
self._cx.execute("PRAGMA journal_mode = wal")
|
||||||
|
|
||||||
if flag == "rwc":
|
if flag == "rwc":
|
||||||
self._execute(BUILD_TABLE)
|
self._execute(BUILD_TABLE)
|
||||||
|
|
||||||
def _execute(self, *args, **kwargs):
|
def _execute(self, *args, **kwargs):
|
||||||
if not self._cx:
|
if not self._cx:
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import os
|
||||||
|
import stat
|
||||||
import sys
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
from contextlib import closing
|
from contextlib import closing
|
||||||
|
|
@ -90,6 +92,49 @@ class ReadOnly(_SQLiteDbmTests):
|
||||||
self.assertEqual([k for k in self.db], [b"key1", b"key2"])
|
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):
|
class ReadWrite(_SQLiteDbmTests):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
Fix opening a :mod:`dbm.sqlite3` database for reading from read-only file
|
||||||
|
or directory.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue