Skip to content

qmd_py.db.models

The SQLModel table definitions: User, Collection, CollectionGrant, Content, Document, LlmCache, EmbeddingModel, and their relationships — the schema Architecture › Storage describes.

models

SQLModel table definitions.

Table classes here are plain SQLAlchemy models underneath (SQLModel is a thin Pydantic layer on top) - all async DB access goes through SQLAlchemy's own AsyncEngine/AsyncSession (see engine.py), never SQLModel's sync-oriented Session/create_engine helpers. Columns SQLModel's type system doesn't know natively (TSVECTOR, pgvector.Vector) use the sa_column= escape hatch, same as plain SQLAlchemy would need.

Note on search_vector: this is a plain, app-maintained TSVECTOR column, NOT a Postgres GENERATED ALWAYS AS (...) STORED column - a generated column's expression can only reference columns in its own table, but the document body lives in Content.doc (joined via hash), not in Document itself. It's kept in sync by update_document_search_vector() in the search module (ported from the TS updateDocumentSearchVector), called wherever a document's title/path/body changes - the same discipline the working TS/Postgres implementation already uses successfully.

Per-model embedding tables (embeddings_<slug>) are NOT defined here - they're created dynamically at runtime once a model's dimension is known (see search/vector.py, Phase 5), mirroring the TS version's lazy vector column creation but non-destructively (one table per model, never dropped on a model switch).

User

Bases: SQLModel

Exactly one row for now (the mocked single-user case) - see auth.py.

global_context is the TS version's context add / ("applies to all collections") - user-scoped here rather than a single system-wide KV row, since in a real multi-user future each user reasonably wants their own global context, not one shared by everyone.

Collection

Bases: SQLModel

Replaces the TS version's bare documents.collection TEXT tag with a real, owned entity - the prerequisite for per-collection ACL. name is scoped per-owner (UNIQUE(owner_user_id, name)), not globally unique like the TS version's store_collections.name primary key.

Per-path context text (the TS version's context add <path> "text", where <path> can be the collection root or a sub-path) lives in CollectionContext, not as a single string field here - the TS model supports many contexts per collection, keyed by path prefix.

CollectionContext

Bases: SQLModel

One row per (collection, path_prefix) context entry - path_prefix is "" for the collection root (context add marq://coll/ with no sub-path), or a relative sub-path (context add marq://coll/notes).

CollectionGrant

Bases: SQLModel

Additive ACL table - unpopulated beyond the implicit owner today. can_access() in auth.py is mocked to always return True regardless of whether a grant row exists; wiring this table into real checks later is additive, not a rearchitecture.

Content

Bases: SQLModel

Content-addressable storage - unchanged from the TS design (it's good, not legacy): identical document bodies dedupe across paths and collections by hash.

Document

Bases: SQLModel

collection_id FK replaces the TS version's bare TEXT tag; created_at/modified_at are real TIMESTAMPTZ (not TS's TEXT ISO strings); active is a real BOOLEAN (not TS's INTEGER 0/1).

LlmCache

Bases: SQLModel

result is JSONB (not TS's TEXT) - queryable, and the natural type for what's always JSON-serialized data anyway.

EmbeddingModel

Bases: SQLModel

Registry of embedding models that have ever been used - each active row corresponds to one dynamically-created embeddings_<slug> table (see search/vector.py, Phase 5), fixing the TS version's one-model-at- a-time limitation.

role class-attribute instance-attribute

role: str = Field(sa_column=Column(Text, nullable=False))

'embed' | 'rerank' | 'generate' - CHECK constraint below, not an enum type.