Skip to content

stack-prisma: auto-derive EQL v3 functional indexes via onFieldEvent codec hook #896

Description

@coderdan

Goal

An encrypted column declared in the contract should get its eql_v3.* functional indexes created by prisma-next migration plan automatically — no hand-written rawSql recipes (today's story, see #895 for the interim doc fix).

Why it's now possible (Prisma Next 0.17)

  • CodecControlHooks.onFieldEvent(event, ctx) (family-sql) fires per added/dropped/altered field, dispatched by the field's codecId, and returns OpFactoryCall[] inlined into the app-space migration's ops.json (ADR 213).
  • target-postgres exposes a createIndex op factory whose elements are {columns} | {expression} — the expression form plus type/where/unique/options extras, rendered as CREATE INDEX ... USING <type> (<expression>).
  • Everything needed to derive the right indexes already exists in packages/stack-prisma/src/v3/catalog.ts: V3DomainMeta carries capabilities and indexes per domain.

Sketch

Implement onFieldEvent in packages/stack-prisma/src/migration/cipherstash-codec-v3.ts (its header currently documents the deliberate absence — that rationale was about v2-style search config, not index DDL):

  • 'added': emit one createIndex per capability the domain carries — eql_v3.eq_term(col) btree (eq-capable text domains), eql_v3.ord_term(col) btree (ord), eql_v3.match_term(col) gin (match), (eql_v3.to_ste_vec_query(col)::jsonb) jsonb_path_ops gin (Json containment).
  • 'dropped': matching dropIndex ops.
  • Deterministic names (<table>_<col>_eq / _ord / _match / _json) — expression indexes require explicit names, and stable names keep re-plans no-op.

Domain rules (from skills/stash-indexing): numeric/date _ord domains get no eq_term index (no overload exists; eq inlines to ord_term = ord_term); TextOrd/TextSearch need both; _ord_ore opclass is superuser-gated → must be opt-in or skipped (Supabase breaks otherwise).

Known hazards to resolve

  1. Introspection round-trip churn: Postgres normalizes stored index expressions (pg_get_indexdef), and the IR treats expression as opaque/never-parsed. If schema-verify compares authored vs introspected strings, every re-plan diffs dirty. Needs a live round-trip test before anything ships.
  2. Codec-swap gap: per ADR 213, a change where only codecId differs fires no 'altered' event — a domain swap (e.g. TextEq → TextSearch) won't re-derive indexes. Handle or document.
  3. ANALYZE: part of the recipe (expression indexes have no statistics until it runs) — emit as a companion op.
  4. Existing pins: test/v3/migration-v3.test.ts and the example e2e assert v3 columns contribute zero extra migration ops; they flip to asserting the derived index set.
  5. Update skills/stash-prisma + skills/stash-indexing again once this lands (auto vs manual story), with a stash changeset; @cipherstash/stack-prisma gets a minor changeset.

Refs: #749 (the 0.17 upgrade), #895 (interim skill fix).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions