Skip to content

feat(db): promote documents.index_generation_id to a typed generated column#640

Merged
BigSimmo merged 1 commit into
mainfrom
claude/documents-generation-column-2026-07-14
Jul 14, 2026
Merged

feat(db): promote documents.index_generation_id to a typed generated column#640
BigSimmo merged 1 commit into
mainfrom
claude/documents-generation-column-2026-07-14

Conversation

@BigSimmo

@BigSimmo BigSimmo commented Jul 14, 2026

Copy link
Copy Markdown
Owner

Summary

Deferred audit item D2 (2026-07-13 audit, finding 1 stretch — staged after the A1 lexical RPC rewrite proved out live at 57ms warm).

The documents table was the last place the committed index-generation pointer lived only in JSONB (metadata->>'index_generation_id'); every retrieval read extracted it per candidate row via is_committed_document_generation(uuid, jsonb). The artifact tables were already promoted in July (20260702180000).

Design: generated column, not dual-write

  • documents.index_generation_id uuid GENERATED ALWAYS AS (…) STORED — a regex-guarded cast of metadata->>'index_generation_id'. JSON stays the source of truth for every writer (commit RPC, batch repair, arbitrary apply_document_metadata_patch callers), so the typed column cannot drift — no dual-write bugs, no trigger, zero writer changes. The ADD COLUMN table rewrite backfills all rows in the same statement (~2k rows, sub-second).
  • Malformed non-UUID values generate NULL instead of failing writes — identical reader semantics to the old byte-sensitive text comparison (committed rows on such a document stay invisible; NULL-generation legacy rows stay visible).
  • is_committed_document_generation gains a (uuid, uuid) overload (same truth table; the (uuid, jsonb) original remains for other callers).
  • The five effective reader functions now compare typed uuid columns: corpus_topic_term_stats, match_document_chunks + match_document_chunks_hybrid (the codified-live pg_dump-style bodies, which win on reset — not the superseded lowercase duplicates, which are also updated for file consistency), match_document_chunks_text, match_document_lookup_chunks_text. Bodies were extracted byte-exact from schema.sql by script with per-function replacement-count assertions; only the one argument changed. _v2 wrappers inherit.

Safety sweeps done

  • No SQL statement joins documents with an artifact table using an unqualified index_generation_id (no new ambiguity).
  • The only wildcard view (document_strict_gate_status) expands an explicit CTE list — no duplicate-column collision on recreation.
  • No TS code writes a table-level index_generation_id on documents (all writes go via the RPC arg into metadata), so the GENERATED column can't be write-conflicted.

Verification

  • Full migration-chain replay from scratch in a supabase/postgres:17.6.1.127 scratch container: ALL MIGRATIONS REPLAYED CLEAN, plus spot checks — column present with attgenerated='s', valid metadata → typed uuid, malformed metadata → NULL without error, overload truth table (null,null)=t / (X,X)=t / (X,null)=filtered. (Local supabase db reset is unavailable on this machine — port 54322 sits in a Windows excluded port range — hence the scratch-container equivalent; CI's db-reset-verify re-validates.)
  • Drift manifest regenerated via the standard Docker replay; diff shows exactly the new column, the new overload, and def-hash changes for the five reader functions.
  • verify:cheap green (2,268 tests), verify:pr-local green (production build + client-bundle scan).

Deploy note

Merge auto-applies the migration to live via the Supabase GitHub integration. The table rewrite is sub-second at this corpus size; function redefinitions are transactional. After live apply, the standard post-migration checks (check:drift, advisors) can be run on request.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved consistency when determining whether document generations are committed.
    • Prevented malformed index-generation metadata from affecting document retrieval.
    • Updated document search and retrieval paths to use reliable typed generation identifiers.
  • Reliability
    • Improved behavior during document reindexing, ensuring replacement generations remain hidden until committed.
    • Preserved existing retrieval results and compatibility for documents using legacy generation metadata.

…column

Audit 2026-07-13 deferred item D2 (finding 1 stretch, staged after the A1
lexical RPC rewrite proved out live).

- documents.index_generation_id is GENERATED ALWAYS AS (metadata->>
  'index_generation_id')::uuid STORED, regex-guarded so malformed values
  generate NULL instead of failing writes. JSON stays the source of truth
  for every writer (commit RPC, batch repair, metadata patches) - the
  column can never drift, no dual-write, no trigger.
- is_committed_document_generation gains a (uuid, uuid) overload (same
  truth table as the (uuid, jsonb) original, which remains).
- The five effective reader functions now compare typed uuid columns
  instead of extracting JSONB per candidate row: corpus_topic_term_stats,
  match_document_chunks + match_document_chunks_hybrid (codified live
  bodies), match_document_chunks_text, match_document_lookup_chunks_text.
  Bodies are byte-exact copies with only that argument changed; _v2
  wrappers inherit. uuid equality is normalized where the old text compare
  was byte-sensitive - unchanged in practice (lowercase UUIDs everywhere).
- schema.sql, drift manifest (regenerated via Docker replay), documents
  Row/Insert/Update types, and schema tests updated in lockstep.

Verified: full migration-chain replay from scratch in a supabase/postgres
scratch container (ALL MIGRATIONS REPLAYED CLEAN) with spot checks for the
stored generated column, malformed-value NULL, and the overload truth
table; verify:cheap green (2,268 tests); verify:pr-local green (build +
client-bundle scan).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@BigSimmo
BigSimmo enabled auto-merge (squash) July 14, 2026 06:25
@supabase

supabase Bot commented Jul 14, 2026

Copy link
Copy Markdown

Updates to Preview Branch (claude/documents-generation-column-2026-07-14) ↗︎

Deployments Status Updated
Database Tue, 14 Jul 2026 06:25:55 UTC
Services Tue, 14 Jul 2026 06:25:55 UTC
APIs Tue, 14 Jul 2026 06:25:55 UTC

Tasks are run on every commit but only new migration files are pushed.
Close and reopen this PR if you want to apply changes from existing seed or migration files.

Tasks Status Updated
Configurations Tue, 14 Jul 2026 06:26:03 UTC
Migrations Tue, 14 Jul 2026 06:27:11 UTC
Seeding Tue, 14 Jul 2026 06:27:16 UTC
Edge Functions Tue, 14 Jul 2026 06:27:16 UTC

View logs for this Workflow Run ↗︎.
Learn more about Supabase for Git ↗︎.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The database schema now exposes document index generations as typed UUIDs derived from metadata. Generation checks and retrieval functions use the typed values, with updated generated types, Drift metadata, schema replay definitions, permissions, and tests.

Changes

Typed generation gating

Layer / File(s) Summary
Typed index generation column
supabase/migrations/..., supabase/schema.sql, supabase/drift-manifest.json, src/lib/supabase/database.types.ts
Adds the generated documents.index_generation_id UUID column, records it in schema metadata, and exposes it in Supabase row, insert, and update types.
Typed commit comparison helper
supabase/migrations/..., supabase/schema.sql
Adds the UUID overload of is_committed_document_generation with legacy-row NULL handling and updates execution privileges.
Migration retrieval updates
supabase/migrations/...
Updates corpus statistics and document retrieval functions to compare chunk and document generation UUIDs while preserving result and ranking behavior.
Schema synchronization and replay validation
supabase/schema.sql, supabase/drift-manifest.json, tests/supabase-schema.test.ts
Updates schema retrieval definitions, function hashes, re-declared wrappers, and replay assertions to use typed generation comparisons.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant RetrievalFunction
  participant DocumentChunk
  participant Documents
  participant CommitHelper
  RetrievalFunction->>DocumentChunk: Read chunk generation UUID
  RetrievalFunction->>Documents: Read document generation UUID
  RetrievalFunction->>CommitHelper: Compare typed generations
  CommitHelper-->>RetrievalFunction: Return committed-generation result
Loading

Possibly related PRs

  • BigSimmo/Database#582: Both changes modify the SQL retrieval and lexical document-chunk matching paths.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The summary and verification are strong, but the template's Clinical Governance Preflight section for Supabase/document-access changes is missing. Add the Clinical Governance Preflight section and fill the relevant Supabase/document-access, service-role, and production-readiness checklist items.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and clearly summarizes the main change: promoting documents.index_generation_id to a typed generated column.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/documents-generation-column-2026-07-14

Comment @coderabbitai help to get the list of available commands.

@BigSimmo
BigSimmo merged commit e60c18b into main Jul 14, 2026
16 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@supabase/migrations/20260714110000_promote_documents_index_generation_id.sql`:
- Around line 33-41: Avoid adding index_generation_id as a stored generated
column in the documents migration, since it rewrites the table and requires an
ACCESS EXCLUSIVE lock. Replace it with a nullable column and implement the
required backfill and trigger synchronization path, preserving the existing UUID
validation behavior; otherwise explicitly move this operation to a documented
maintenance-window migration.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: fffd1a47-6053-4dbe-964d-2d877de79685

📥 Commits

Reviewing files that changed from the base of the PR and between 85137cd and 57b8bd3.

📒 Files selected for processing (5)
  • src/lib/supabase/database.types.ts
  • supabase/drift-manifest.json
  • supabase/migrations/20260714110000_promote_documents_index_generation_id.sql
  • supabase/schema.sql
  • tests/supabase-schema.test.ts

Comment on lines +33 to +41
alter table public.documents
add column if not exists index_generation_id uuid generated always as (
case
when metadata->>'index_generation_id'
~ '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
then (metadata->>'index_generation_id')::uuid
else null
end
) stored;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== File list ==\n'
git ls-files supabase/migrations/20260714110000_promote_documents_index_generation_id.sql

printf '\n== File outline ==\n'
ast-grep outline supabase/migrations/20260714110000_promote_documents_index_generation_id.sql --view expanded || true

printf '\n== Relevant lines ==\n'
cat -n supabase/migrations/20260714110000_promote_documents_index_generation_id.sql | sed -n '1,140p'

Repository: BigSimmo/Database

Length of output: 7190


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Search for similar generated-column migrations ==\n'
rg -n "generated always as|stored;" supabase/migrations -S || true

printf '\n== Search for documents table definition ==\n'
rg -n "create table public.documents|table public.documents|metadata->>'index_generation_id'|index_generation_id" supabase -S || true

Repository: BigSimmo/Database

Length of output: 50373


🌐 Web query:

PostgreSQL ALTER TABLE ADD COLUMN GENERATED ALWAYS AS STORED table rewrite lock behavior documentation

💡 Result:

In PostgreSQL, adding a column defined as GENERATED ALWAYS AS (...) STORED requires a full table rewrite, which necessitates an ACCESS EXCLUSIVE lock on the table [1][2]. This lock persists for the entire duration of the rewrite process, meaning it blocks all read and write operations on the table for that time [1]. Official PostgreSQL documentation confirms that unlike adding a column with a standard, non-volatile DEFAULT value—which can often be performed quickly without a rewrite—adding a stored generated column triggers a complete rewrite of the table and its indexes [2]. There is currently no native, built-in mechanism to add such a column without this heavy locking behavior [1]. Technical discussions within the PostgreSQL community have acknowledged this limitation, with ongoing efforts and proposals to enable adding stored generated columns without long-lived locks (e.g., by validating an existing check constraint to skip the rewrite), but these are not yet standard features in the current released versions of PostgreSQL [3][4][5]. Consequently, for production tables with a large number of rows, this operation can cause significant downtime or unavailability [6][1]. Typical workarounds involve using virtual generated columns (which do not require a rewrite) or implementing application-level logic/triggers if a stored column is strictly required [1].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Inspect migration content ==\n'
sed -n '1,220p' supabase/migrations/20260714110000_promote_documents_index_generation_id.sql

printf '\n== Inspect documents table definition location(s) ==\n'
rg -n "create table public\.documents|create table .*documents|index_generation_id|metadata" supabase/migrations supabase -S

Repository: BigSimmo/Database

Length of output: 50374


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Print only the migration and any nearby references to documents schema evolution.
sed -n '1,220p' supabase/migrations/20260714110000_promote_documents_index_generation_id.sql

printf '\n--- nearby document migrations ---\n'
git ls-files supabase/migrations | rg 'documents|index_generation_id|promote_documents'

Repository: BigSimmo/Database

Length of output: 12965


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Prior migration introducing index_generation_id ==\n'
sed -n '1,220p' supabase/migrations/20260702180000_promote_index_generation_id_columns.sql

printf '\n== Documents table definition in schema snapshot ==\n'
sed -n '61,110p' supabase/schema.sql

printf '\n== Search for other documents ALTERs around index_generation_id ==\n'
rg -n "documents.*index_generation_id|index_generation_id.*documents|add column .*index_generation_id uuid generated always" supabase/migrations/2026070* supabase/schema.sql -S

Repository: BigSimmo/Database

Length of output: 11341


Avoid the table rewrite here. ADD COLUMN ... STORED rewrites public.documents and holds an ACCESS EXCLUSIVE lock for the duration, which can block all document reads and writes on a large table. Use a nullable column plus backfill/trigger path, or schedule a maintenance window if downtime is acceptable.

🧰 Tools
🪛 Squawk (2.59.0)

[warning] 34-41: Adding a generated column requires a table rewrite with an ACCESS EXCLUSIVE lock. In Postgres versions 11+, non-VOLATILE DEFAULTs can be added without a rewrite. Add the column as nullable, backfill existing rows, and add a trigger to update the column on write instead.

(adding-field-with-default)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@supabase/migrations/20260714110000_promote_documents_index_generation_id.sql`
around lines 33 - 41, Avoid adding index_generation_id as a stored generated
column in the documents migration, since it rewrites the table and requires an
ACCESS EXCLUSIVE lock. Replace it with a nullable column and implement the
required backfill and trigger synchronization path, preserving the existing UUID
validation behavior; otherwise explicitly move this operation to a documented
maintenance-window migration.

Source: Linters/SAST tools

@BigSimmo
BigSimmo deleted the claude/documents-generation-column-2026-07-14 branch July 14, 2026 07:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant