Skip to content

aegean.db

SQLite persistence for a Corpus: a faithful, queryable round-trip (documents + tokens + provenance) with an optional FTS5 full-text index, plus lazy streaming. Stdlib sqlite3 only. See also Corpus.to_sql / Corpus.from_sql.

db

SQLite persistence for a Corpus — stdlib sqlite3 only, so the core stays dependency-free (the same precedent as aegean.cache).

to_sqlite / from_sqlite are a faithful, queryable round-trip: documents and tokens are normalized into rows (so SQL and full-text search work over them), with the nested structure (signs, alternate readings, annotations, line groupings, image refs, notes) kept in JSON columns. Provenance and the sign inventory live in a small key/value meta table. search matches a whole token by default (an FTS5 phrase when available, else an indexed exact-match query on tokens(text)); mode="substring" is the opt-in within-token search (a casefolded Python scan, since SQLite's LIKE folds ASCII case only). stream yields documents lazily for a large DB-backed corpus without materializing it.

A corpus out of the database cites exactly like a corpus out of JSON — the provenance round- trips, and a database grown with append=True records every appended corpus's provenance, so the reloaded corpus cites them all. Corpus.to_sql / Corpus.from_sql are thin wrappers over these functions.

to_sqlite

to_sqlite(corpus: Corpus, path: str | Path, *, fts: bool = True, append: bool = False, progress: Callable[[int, int], None] | None = None) -> None

Write corpus to a SQLite database at path.

By default this overwrites any existing file. With append=True it instead upserts corpus's documents into an existing database (by document id — a document with a matching id is replaced, others are added), keeping the rest intact; the FTS5 index is refreshed, and the appended corpus's provenance is recorded alongside the original's, so the reloaded corpus cites every source that went in (see from_sqlite). Documents and tokens become queryable rows; provenance and the sign inventory live in a meta table. Round-trips losslessly via from_sqlite.

progress (optional) is called as progress(done, total) after each document is written, counting documents — the hook a minutes-long write (a DDbDP-sized corpus is ~57k documents) reports through; the CLI paints a live line from it. The written database is identical with or without it; the final call is (total, total), made before the FTS index build (one bulk statement, not per-document).

from_sqlite

from_sqlite(path: str | Path, *, progress: Callable[[int, int], None] | None = None) -> Corpus

Reconstruct the Corpus written by to_sqlite — documents, sign inventory, and provenance, byte-for-byte equivalent to the original. A database grown with to_sqlite(append=True) stores one provenance per appended source; those come back as a single combined provenance naming every source, so Corpus.cite stays truthful.

progress (optional) is called as progress(done, total) after each document is materialized, counting documents — the hook the ~100 s DDbDP whole-corpus load (aegean.load("ddbdp")) reports through. The returned corpus is identical with or without it; an empty database makes no calls, and the final call is (total, total).

search

search(path: str | Path, query: str, *, limit: int = 50, mode: str = 'token') -> list[tuple[str, int | None, str]]

Search a SQLite corpus's tokens; returns (doc_id, position, text) hits.

position is the token's stored position, or None for a token saved without one (a supported state since 0.19.4: an appended token keeps position=None).

mode="token" (default) matches a whole token literally: the query must equal the token, so a transliteration with hyphens (KU-RO, A-DU) matches only that token and never a longer token that merely contains it (KU-RO does not match PO-TO-KU-RO). It uses the FTS5 index when present (then confirms the exact match); without FTS5 it falls back to an indexed exact-match query for an ASCII query, or a Python scan for a non-ASCII one (SQLite's NOCASE folds ASCII case only).

mode="substring" matches the query as a substring of a token, so KU-RO also finds PO-TO-KU-RO — useful for tracing every token a sign-group occurs in. It folds case in Python (SQLite's LIKE also folds ASCII only), scanning the token table: about 4 ms on the bundled 1,721-document Linear A corpus, linear in the token count.

Both modes fold case, Greek included (ku-ro finds KU-RO; λόγος finds ΛΌΓΟΣ, final sigma folding with the rest). Diacritics still have to match: λογος does not find λόγος.

limit caps the number of hits (default 50); zero or a negative value returns every match. The database is opened read-only (a sqlite mode=ro URI), so a search can never create or modify a file: a missing path or a non-SQLite file raises sqlite3.OperationalError / sqlite3.DatabaseError instead of leaving an empty database behind.

stream

stream(path: str | Path) -> Iterator[Document]

Yield the documents of a SQLite corpus one at a time, without materializing the corpus.

A separate read cursor fetches each document's tokens on demand, so memory stays flat for arbitrarily large databases — the DB-backed counterpart to a streamed load (item: large corpora). Pairs with from_sqlite when random access is wanted instead.