lacing.store.sqlite#
SQLite-backed annotation store and the .annot portable file format.
A SqliteStore is both:
A persistent backend implementing the
IntervalAnnotationStoreprotocol over a single SQLite file.The on-disk shape of the
.annotportable handoff format (BACK-DOC §3.1: “SQLite-as-app-format” — Git-trackable, email-attachable, single-file).
Design notes:
The integer pair
(start_value, start_rate)and(end_value, end_rate)are the source of truth for time. Rational arithmetic stays exact.The R*Tree index uses
REAL(float seconds) because SQLite’s R*Tree requires it. We treat R*Tree results as a pre-filter and re-check boundary cases against the integer-pair columns. Any annotation whose rational seconds matches the query’s bounds exactly will still be filtered correctly because we widen the R*Tree query by one ULP.Tier hierarchy is enforced via FK; stereotype validation is the application’s job (see
lacing.tier.validate_tier_constraint).schema_versionlives in themetatable; migrations register upgrade functions keyed on the from-version inlacing.store.migrations(the store-level counterpart of thebody ladder in
lacing.schema). Opening a stale file refuses unless asked to migrate (SqliteStore(path, migrate=True)orlacing migrate <path>), and refuses before touching the file.
See lacing-architecture (Phase 1) and BACK-DOC §3.1 for the why.
Module Attributes
Current |
Functions
|
Persist an in-memory store to a new |
|
Load a |
Classes
|
SQLite-backed |
Exceptions
Raised when opening a |
- lacing.store.sqlite.SCHEMA_VERSION = 2#
Current
.annotschema version. Increment + register a migration (lacing.store.migrations.register_store_migration(), withstore_kind="sqlite") when making a breaking change to the table layout.v2 (lacing#14, defect D5):
prov_was_derived_frommay contain 64-hex artifactasset_idstrings alongside annotation UUIDs. The table layout and every existing row are unchanged — the bump exists because **pre-v2 builds eagerlyUUID()-parse the column on read** and crash on the first asset id, so they must refuse v2 files instead of opening them and failing row-by-row. The v1→v2 migration step is accordingly stamp-only (seelacing.store.migrations).Operational caveat: the version gate runs at open. A pre-v2 build’s already-open connection survives the stamp and keeps reading — safe until some v2 writer mints an asset-id ref, at which point it fails as a raw
ValueErrormid-read rather than a refusal. Restart long-lived pre-v2 services after migrating a file they serve.
- exception lacing.store.sqlite.SchemaMismatchError[source]#
Bases:
RuntimeErrorRaised when opening a
.annotfile with an incompatible schema.
- class lacing.store.sqlite.SqliteStore(path, *, check_same_thread=True, migrate=False)[source]#
Bases:
objectSQLite-backed
IntervalAnnotationStore.- Parameters:
path (
str|PathLike) – Path to the.annotfile. Use":memory:"for an ephemeral in-memory database.check_same_thread (
bool) – Forwarded tosqlite3.connect. We hold a single connection guarded by a lock; passFalsewhen sharing across threads.migrate (
bool) – Opt-in to upgrading a file written at an olderschema_versionon open, via the ladder inlacing.store.migrations. Off by default — silently rewriting someone’s file on open is worse than refusing. Every open-time schema failure — refusal or failed migration — raisesSchemaMismatchError; a failed migration chains the ladder’sStoreMigrationErroras its cause.
- lock(key)[source]#
Exclusive lock named
key: a file lock beside the database.Excludes other threads and other processes on this host that open the same
.annotfile; the kernel frees it if the holder dies. An in-memory database (":memory:") has no file, so it falls back to a process-local lock.- Return type:
- update(annotation)[source]#
Replace the row with this id (and its R*Tree entry) in one transaction.
Readers take the same lock (
_fetchall()), so on a connection shared between threads — where a reader would otherwise see this transaction’s uncommitted state — they get the old annotation or the new one, never neither (lacing#66).- Return type: