synology_apm_repo.sdk.storage.sqlite module

SQLite access helper — a free function, not an ObjectStore Protocol method, since WAL handling is shared logic every backend needs identically.

Two paths:

  • Fast path: no non-zero -wal sidecar present -> open directly via a read-only, immutable file: URI. Only valid for LocalFsStore — the only place in the SDK that assumes a store’s paths map onto real filesystem paths, which is why this is a free function rather than a method every backend must implement; S3/Azure stores always take the slow path.

  • Slow path: a non-zero -wal sidecar exists (real observed case: copy_meta_file/target.db) -> copy the main file plus any present -wal/-shm sidecars into a temp directory and open that copy read-write (letting SQLite’s own recovery replay the WAL), so the original store’s bytes are never touched. Only -wal’s size gates this decision — a non-zero -shm alone does not trigger it.

The read-only/read-write split between the two is the whole reason apply_index_hint lives here rather than in storage/table.py: only a connection onto a copy we own can be given an index, and this module is where that distinction is made.

async synology_apm_repo.sdk.storage.sqlite.open_sqlite(store, path, *, tmp_dir=None, transform=None)

Open an aiosqlite connection to path within store — read-only on the fast path, read-write on the slow path’s own private materialized copy. The slow path is taken whenever transform is given, store isn’t a LocalFsStore, or a non-zero -wal sidecar is present; the fast path is the LocalFsStore-only case with none of those.

Returns (connection, materialized_tmp_dir). materialized_tmp_dir is None on the fast path (nothing extra was created); on the slow path it is the tempfile.TemporaryDirectory backing the materialized copy — the caller must keep a reference to it for as long as connection is used, and should call .cleanup() (or simply let it go out of scope) once done.

transform, when given, is applied independently to each file’s raw bytes (main file, and any -wal/-shm sidecar) before materializing, and forces the slow path regardless of WAL state.

aiosqlite pins each connection to one dedicated thread for its lifetime, funnelling every statement — including close() — through it, so check_same_thread is unnecessary.

async synology_apm_repo.sdk.storage.sqlite.apply_index_hint(conn, table, columns)

A pure declaration of intent — “queries against table will filter/sort by columns” — never a command this is guaranteed to fulfil; callers never need to check whether it actually did anything. Safe to call even when columns is already known to be indexed (e.g. a schema-defined index or a PRIMARY KEY): the leading-prefix check below makes that case a cheap no-op, so callers apply this uniformly rather than reasoning per call site about whether it’s needed.

Checks whether an existing index already covers columns as a leading prefix; skips if so. Otherwise attempts CREATE INDEX IF NOT EXISTS and lets a genuinely read-only connection’s own answer settle whether that’s even possible: SQLite raises OperationalError: attempt to write a readonly database synchronously and safely against a mode=ro connection, caught here and treated as “the hint quietly did nothing” — the caller’s own query afterward just costs a full scan. Any other error still propagates.