Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/development/database-tooling.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ Mediaforce's SQLite schema.

## Runtime transaction boundaries

- SQLite file URIs retain their own path escaping inside a SQLAlchemy URL.
Build and render the outer URL with SQLAlchemy's `URL.create` so SQLAlchemy
2.1's database-name decoding preserves the exact filename passed to SQLite,
including spaces, query delimiters, and percent signs. Regression coverage
opens the same file through writable, guarded, and read-only engines.
- Writable `open_db()` calls automatically honor any active Mediaforce runtime
lease. The lease pins one database inode, guards every migration and SQLite
connection before SQL is issued, and rejects pathname replacement instead of
Expand Down
8 changes: 7 additions & 1 deletion mediaforce/core/db_migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from sqlalchemy import create_engine, event
from sqlalchemy.engine import Connection
from sqlalchemy.engine import Engine
from sqlalchemy.engine import URL
from sqlalchemy.pool import NullPool

from mediaforce.core.db_custody import DatabaseCustodyBorrow
Expand Down Expand Up @@ -119,7 +120,12 @@ def readonly_database_url(db_path: Path) -> str:

def _database_uri_url(db_path: Path, *, mode: str) -> str:
quoted_path = quote(os.fspath(db_path), safe="/")
return f"sqlite+pysqlite:///file:{quoted_path}?mode={mode}&uri=true"
url = URL.create(
"sqlite+pysqlite",
database=f"file:{quoted_path}",
query={"mode": mode, "uri": "true"},
)
return url.render_as_string(hide_password=False)


@contextmanager
Expand Down
9 changes: 6 additions & 3 deletions tests/test_db_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from alembic import command
from sqlalchemy import create_engine
from sqlalchemy import inspect
from sqlalchemy import make_url
from sqlalchemy import select
from sqlalchemy.exc import OperationalError

Expand Down Expand Up @@ -6034,9 +6035,11 @@ def test_sqlite_urls_quote_legal_special_characters_without_changing_path(self)
db_path = Path(temp_dir) / "library space#?%:.sqlite3"
writable_url = database_url(db_path)
readonly_url = readonly_database_url(db_path)
for encoded_character in ("%20", "%23", "%3F", "%25", "%3A"):
self.assertIn(encoded_character, writable_url)
self.assertIn(encoded_character, readonly_url)
for url in (writable_url, readonly_url):
database_uri = make_url(url).database
assert database_uri is not None
for encoded_character in ("%20", "%23", "%3F", "%25", "%3A"):
self.assertIn(encoded_character, database_uri)

engine = create_engine_for_path(db_path)
try:
Expand Down
Loading
Loading