Anonymous View
Skip to content

Epic: PostgreSQL backend support for the cq server #257

@nissessenap

Description

@nissessenap

Summary

Add opt-in PostgreSQL support to the cq server. SQLite remains the
zero-config default for single-instance use; PostgreSQL is opt-in for
multi-process / Kubernetes deployments. Selection via CQ_DATABASE_URL
with fallback to CQ_DB_PATH.

Server only. Go SDK, Python SDK LocalStore, and CLI local store
remain SQLite — those are offline (local) stores, not shared databases.

This epic tracks the implementation agreed in RFC #275.

Original request

Preserved from the issue history above (opened by @nissessenap,
2026-04-09): hosting cq in Kubernetes with a single SQLite instance is
not viable — backup pain, single-writer ceiling, no path to horizontal
scale.

Architecture (from RFC #275)

  • Store protocol (async) with one concrete implementation per backend.
    • SqliteStore — owns sqlite-vec, FTS5, PRAGMAs, anything SQLite-native.
    • PostgresStore — owns pgvector, tsvector + GIN, advisory locks,
      anything Postgres-native.
  • Portable queries live in a shared SQLAlchemy Core helper consumed by
    both concrete stores. Strategy pattern at the protocol boundary; no
    runtime if dialect == ... branches inside methods.
  • Migrations: Alembic with render_as_batch=True. Single migration
    files run on both backends.
  • pg_advisory_lock wraps migration on Postgres to prevent multi-pod
    startup race.
  • Baseline migration reverse-engineered from current production schema,
    stamped on first startup of existing databases.
  • Timestamps cleaned up to proper datetime at store boundary
    (TIMESTAMP WITH TIME ZONE on Postgres, ISO strings on SQLite).

Phased plan

Phase 1 — make the change easy (SQLite-only, no behavior change):
extension point + migration framework. Mergeable independently.

Phase 2 — make the easy change: drop in PostgresStore, CI matrix.

Phase 3 — timestamp cleanup: proper datetime at store boundary.

Phase 4 — documentation.

Phase 1 dependency graph

Level 0:  (1) deps + Alembic skeleton
              │
              ├──────────────┬───────────────┐
              ▼              ▼               ▼
Level 1:  (2) baseline    (3) async      (4) Core query
          + stamp           Store           helpers
                            protocol
              │              │               │
              │              └───────┬───────┘
              │                      ▼
Level 2:      │             (5) SqliteStore
              │                  via protocol
              │                      │
              │                      ▼
Level 3:      │             (6) CQ_DATABASE_URL
              │                  + factory
              │
              ▼
Level 4:  (7) remove ad-hoc _ensure_* DDL
              (gate on (2) deployed)

After (1) lands, (2), (3), and (4) can proceed in parallel. (5) waits
on (3) + (4). (6) waits on (5). (7) waits on (2) being deployed
everywhere.

Child issues — Phase 1: protocol + Alembic, SQLite-only

Child issues — Phase 2: PostgresStore

Child issues — Phase 3: timestamp cleanup

Child issues — Phase 4: documentation

Metadata

Metadata

Assignees

No one assigned

    Labels

    acceptedTriaged and approved for implementationenhancementNew feature or requestepicUmbrella issue tracking multi-issue work

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions