64 lines
2.5 KiB
Python
64 lines
2.5 KiB
Python
import sqlalchemy as sa
|
|
from sqlalchemy.engine import URL, make_url
|
|
from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine
|
|
|
|
from haiku.rag.config.models import QueueConfig
|
|
from haiku.rag.ingester.queue.db import (
|
|
SCHEMA_VERSION,
|
|
install_sqlite_pragmas,
|
|
metadata,
|
|
schema_version,
|
|
)
|
|
|
|
__all__ = ["SCHEMA_VERSION", "apply_migrations", "make_engine", "open_queue"]
|
|
|
|
|
|
def make_engine(config: QueueConfig) -> AsyncEngine:
|
|
"""Build the queue's AsyncEngine from config. Uses `dburi` when set,
|
|
otherwise a `sqlite+aiosqlite` URL pointing at the resolved `path`
|
|
(creating the parent directory). SQLite is capped to a single pooled
|
|
connection so the claim stays atomic without row locks; Postgres uses
|
|
pool_pre_ping so a long-running ingester survives a DB restart or idle
|
|
connection drop."""
|
|
if config.dburi:
|
|
url = make_url(config.dburi)
|
|
else:
|
|
path = config.path.expanduser().resolve()
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
# URL.create keeps the path literal — building a string and reparsing
|
|
# would treat `?`/`#` in the filename as query/fragment.
|
|
url = URL.create("sqlite+aiosqlite", database=str(path))
|
|
|
|
if url.get_backend_name() == "sqlite":
|
|
engine = create_async_engine(url, pool_size=1, max_overflow=0)
|
|
else:
|
|
engine = create_async_engine(url, pool_pre_ping=True)
|
|
install_sqlite_pragmas(engine)
|
|
return engine
|
|
|
|
|
|
async def apply_migrations(engine: AsyncEngine) -> int:
|
|
"""Idempotently create tables/indexes and pin schema_version.
|
|
|
|
Returns the schema version after the call. Safe on a fresh DB or one
|
|
already at the latest version.
|
|
"""
|
|
async with engine.begin() as conn:
|
|
await conn.run_sync(metadata.create_all)
|
|
current = (
|
|
await conn.execute(sa.select(schema_version.c.version).limit(1))
|
|
).scalar_one_or_none()
|
|
if current is None:
|
|
await conn.execute(sa.insert(schema_version).values(version=SCHEMA_VERSION))
|
|
elif current < SCHEMA_VERSION: # pragma: no cover - no migrations yet
|
|
# No diff migrations exist yet — future versions add ALTER/UPDATE
|
|
# statements between create_all and the version bump.
|
|
await conn.execute(sa.update(schema_version).values(version=SCHEMA_VERSION))
|
|
return SCHEMA_VERSION
|
|
|
|
|
|
async def open_queue(config: QueueConfig) -> AsyncEngine:
|
|
"""Build the queue engine and ensure its schema is up to date."""
|
|
engine = make_engine(config)
|
|
await apply_migrations(engine)
|
|
return engine
|