"""Add a full-text (tsvector) column + GIN index to every RAG chunk table. Hybrid retrieval fuses pgvector cosine similarity with Postgres native full-text search (keyword/BM25-style) so recall no longer depends on a per-query HyDE LLM call. Each ``chunks_`` table gets a generated ``tsv`` column (``to_tsvector('english', content)``, auto-maintained on insert/update) and a GIN index over it. The work runs inside a plpgsql ``DO`` block guarding on ``information_schema``, so it is idempotent, offline-renderable (``alembic --sql``), and safe whether a table is present, already has ``tsv``, or is absent (e.g. ``chunks_code``). Depends on 030 having reshaped the column to ``content``. Revision ID: 031_rag_chunks_fulltext Revises: 030_rag_chunks_content_schema Create Date: 2026-06-15 """ from __future__ import annotations from alembic import op revision = "031_rag_chunks_fulltext" down_revision = "030_rag_chunks_content_schema" branch_labels = None depends_on = None CHUNK_TABLES = ( "chunks_code", "chunks_documentation", "chunks_journals", "chunks_errors", "chunks_standards", "chunks_decisions", "chunks_reviews", "chunks_learnings", ) _TABLES_SQL = ", ".join(f"'{name}'" for name in CHUNK_TABLES) def upgrade() -> None: op.execute( f""" DO $$ DECLARE t text; BEGIN FOREACH t IN ARRAY ARRAY[{_TABLES_SQL}] LOOP IF EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = t AND column_name = 'content' ) THEN IF NOT EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = t AND column_name = 'tsv' ) THEN EXECUTE format( 'ALTER TABLE %I ADD COLUMN tsv tsvector ' 'GENERATED ALWAYS AS ' '(to_tsvector(''english'', content)) STORED', t ); END IF; EXECUTE format( 'CREATE INDEX IF NOT EXISTS %I ON %I USING gin (tsv)', t || '_tsv_idx', t ); END IF; END LOOP; END $$; """ ) def downgrade() -> None: op.execute( f""" DO $$ DECLARE t text; BEGIN FOREACH t IN ARRAY ARRAY[{_TABLES_SQL}] LOOP EXECUTE format('DROP INDEX IF EXISTS %I', t || '_tsv_idx'); EXECUTE format( 'ALTER TABLE IF EXISTS %I DROP COLUMN IF EXISTS tsv', t ); END LOOP; END $$; """ )