
    #jͲ                     T   d Z ddlZddlZddlZddlZddlZddlmZ ddlm	Z	 ddl
mZ ddlmZ ddlmZmZmZ dd	lmZmZmZmZmZmZmZ dd
lmZmZ ddlmZ  ej        e           Z!dZ" ej#                    Z$d%de%de%fdZ&dede%ddfdZ'de%de(fdZ)d&de%de%de%dz  ddfdZ*	 	 	 d'de%de%dz  de%dz  de%dz  ddf
dZ+	 d(de%dz  de%dz  de,e%dz  e%dz  f         fdZ-dede%de%de(de%ddfdZ.	 	 d)de%de(de%dz  de%ddf
d Z/	 	 d*de%de%de%dz  ddfd!Z0	 	 d+de%d#e%de%dz  ddfd$Z1dS ),a5  
Database migration management using Alembic.

This module provides programmatic access to run database migrations
on application startup. It is designed to be safe for concurrent
execution using PostgreSQL advisory locks to coordinate between
distributed workers.

Supports multi-tenant schema isolation: migrations can target a specific
PostgreSQL schema, allowing each tenant to have isolated tables.

Important: All migrations must be backward-compatible to allow
safe rolling deployments.

No alembic.ini required - all configuration is done programmatically.
    N)Path)command)Config)ResolutionError)
Connectioncreate_enginetext   )bootstrap_extensiondetect_vector_extensionindex_type_keywordindex_using_clauseminimum_rows_for_indexshould_defer_index_creationuses_per_bank_vector_indexes)is_oracle_urlto_libpq_url)mask_network_locationi[pgvectorvector_extensionreturnc                 "    t          | |          S )zJValidate configured vector extension and preserve Azure DiskANN detection.)r   )connr   s     `/home/rurouni/.hermes/hermes-agent/venv/lib/python3.11/site-packages/hindsight_api/migrations.py_detect_vector_extensionr   7   s    "4)9:::    r   schema_namec           
      4   |                      t          d          d|i                                          }|                    dd          }|D ]G}|d                             dd          }|                      t          d| d| d                     HdS )	zNDrop per-bank partial memory_units vector indexes after global ScaNN is ready.z
            SELECT indexname
            FROM pg_indexes
            WHERE schemaname = :schema_name
              AND tablename = 'memory_units'
              AND indexname LIKE 'idx_mu_emb_%'
              AND indexdef LIKE '%embedding%'
        r   "z""r   zDROP INDEX IF EXISTS "z"."N)executer	   fetchallreplace)r   r   rowssafe_schemarow
safe_indexs         r   _drop_per_bank_vector_indexesr'   <   s    <<  	 	 
$
 
 hjj 	 %%c400K S SV^^C..
TP;PP:PPPQQRRRRS Sr   schemac                     t          j        |                                                                           dd         }t                              |d          dz  S )z{
    Generate a unique advisory lock ID for a schema.

    Uses hash of schema name to create a deterministic lock ID.
    N   big)	byteorderl        )hashlibsha256encodedigestint
from_bytes)r(   
hash_bytess     r   _get_schema_lock_idr4   P   sK     007799"1"=J>>*>66%@@r   database_urlscript_locationc                    |pd}t                               d| d           t                               dt          |                       t                               d|            t                      }|                    d|           |                    d|            |                    dd	           |                    d
d           |r|                    d|           	 t
          5  t          j        |d           ddd           n# 1 swxY w Y   n5# t          $ r(}t           	                    d|            Y d}~dS d}~ww xY wt                               d| d           dS )z
    Internal function to run migrations without locking.

    Args:
        database_url: SQLAlchemy database URL
        script_location: Path to alembic scripts
        schema: Target schema (None for default/public)
    publicz0Running database migrations to head for schema 'z'...zDatabase URL: zScript location: r6   zsqlalchemy.urlprepend_sys_path.path_separatorostarget_schemaheadsNzDatabase is at a newer migration revision than this code version knows about. This is expected during rolling deployments. Skipping migrations. Error: z7Database migrations completed successfully for schema '')
loggerinfor   r   set_main_option_alembic_lockr   upgrader   warning)r5   r6   r(   r   alembic_cfges         r   _run_migrations_internalrH   \   s    $HK
KKT;TTTUUU
KKF!6|!D!DFFGGG
KK5O55666 ((K  1?CCC  0,???  2C888  0$777  =##OV<<<
 	2 	2OK111	2 	2 	2 	2 	2 	2 	2 	2 	2 	2 	2 	2 	2 	2 	2 	 	 	
 	\XY\ \	
 	
 	
 		 KKX+XXXYYYYYs<   )D 0DD DD DD 
E(EEmigration_database_urlc           
         |p| }t          |          r|nt          |          }	 |+t          t                    j        }t          |dz            }t          |          }|                                st          d| d          t          |          rt          |||           dS |rt          |          nt          }|pd}	t          |          }
|
                                5 }t                              d|	 d| d	           	 |                    t!          d| d                                                    }|rn)|                                 t'          j        d           d|                                 t                              d           	 t                              d           |                    t!          d                                                    }|r,|d         }|dk    rt                              d           nDt                              d| d           	 |                    t!          d                     |                    t!          d                     |                    t!          d                     |                                 t                              d           n# t0          $ r@}t                              d| d| d           |                                 Y d}~nJd}~ww xY wt                              d           	 |                    t!          d                     |                    t!          d                     |                                 t                              d           n# t0          $ r}|                                 |                    t!          d                                                    }|r%t                              d|d           d!           n.t                              d"| d#           t7          d$          |Y d}~nd}~ww xY wt9          j        d%d&                                          }t?          ||           |                                 t          |||           |                    t!          d'| d                     t                              d(           nE# |                    t!          d'| d                     t                              d(           w xY w	 ddd           dS # 1 swxY w Y   dS # t          $ r t                              d|             t@          $ rA}t                              d)|j!         d
*           t7          d+|j!                   |d}~wt0          $ r4}t                              d,| d
*           t7          d-          |d}~ww xY w).ar  
    Run database migrations to the latest version using programmatic Alembic configuration.

    This function is safe to call from multiple distributed workers simultaneously:
    - Uses PostgreSQL advisory lock to ensure only one worker runs migrations at a time
    - Other workers wait for the lock, then verify migrations are complete
    - If schema is already up-to-date, this is a fast no-op

    Supports multi-tenant schema isolation: when a schema is specified, migrations
    run in that schema instead of public. This allows tenant extensions to provision
    new tenant schemas with their own isolated tables.

    Args:
        database_url: SQLAlchemy database URL (e.g., "postgresql://user:pass@host/db")
        script_location: Path to alembic migrations directory (e.g., "/path/to/alembic").
                        If None, defaults to hindsight-api/alembic directory.
        schema: Target PostgreSQL schema name. If None, uses default (public).
                When specified, creates the schema if needed and runs migrations there.

    Raises:
        RuntimeError: If migrations fail to complete
        FileNotFoundError: If script_location doesn't exist

    Example:
        # Using default location and public schema
        run_migrations("postgresql://user:pass@host/db")

        # Run migrations for a specific tenant schema
        run_migrations("postgresql://user:pass@host/db", schema="tenant_acme")

        # Using custom location (when importing from another project)
        run_migrations(
            "postgresql://user:pass@host/db",
            script_location="/path/to/copied/_alembic"
        )
    Nalembicz%Alembic script location not found at z$. Database migrations cannot be run.)r(   r8   z.Acquiring migration advisory lock for schema 'z' (id=z)...TzSELECT pg_try_advisory_lock()g      ?z Migration advisory lock acquiredz+Checking pgvector extension availability...zrSELECT extname, nspname FROM pg_extension e JOIN pg_namespace n ON e.extnamespace = n.oid WHERE extname = 'vector'r
   z8pgvector extension found in public schema - ready to usez$pgvector extension found in schema 'z0' instead of 'public'. Attempting to relocate...zDROP EXTENSION vector CASCADEzSET search_path TO publiczCREATE EXTENSION vectorz-pgvector extension relocated to public schemaz8Could not relocate pgvector extension to public schema: z . Continuing with extension in 'z	' schema.z6pgvector extension not found, attempting to install...z-pgvector extension installed in public schemaziSELECT nspname FROM pg_extension e JOIN pg_namespace n ON e.extnamespace = n.oid WHERE extname = 'vector'zTCould not install pgvector extension (permission denied?), but extension exists in 'r   z' schema. Continuing...z=pgvector extension is not installed and cannot be installed: zy. Please ensure pgvector is installed by a database administrator. See: https://github.com/pgvector/pgvector#installationzbpgvector extension is required but not installed. Please install it with: CREATE EXTENSION vector;HINDSIGHT_API_VECTOR_EXTENSIONr   zSELECT pg_advisory_unlock(z Migration advisory lock releasedz%Alembic called sys.exit() with code: )exc_infoz)Database migration failed with exit code z#Failed to run database migrations: zDatabase migration failed)"r   r   r   __file__parentstrexistsFileNotFoundErrorrH   r4   MIGRATION_LOCK_IDr   connectr@   debugr    r	   scalarcommittimesleepfetchonerA   rE   	ExceptionrollbackerrorRuntimeErrorr<   getenvlowerr   
SystemExitcode)r5   r6   r(   rI   raw_urlmigration_urlpackage_dirscript_pathlock_idr   enginer   acquired	ext_check
ext_schemarG   ext_recheckr   s                     r   run_migrationsrn      s   ` %4G  -W55PGG<;P;PMd?" x../K!+	"9::O?++!!## 	#mmmm   '' 	$]OFSSSSF 28N%f---=N(& }--^^ m	ALLj+jj]djjjkkk <<-VG-V-V-V(W(WXX__aa  
3  KKMMMLL;<<<[A JKKK !LL3   (**   ;%!*1J!X--$^____ 9: 9 9 9  , LL.M)N)NOOO LL.I)J)JKKK LL.G)H)HIII KKMMM"KK(WXXXX( , , ,"NN!W[\ !W !WAK!W !W !W   !MMOOOOOOOO, KK XYYY %T*E%F%FGGGT*C%D%DEEE$STTTT$ % % % &*ll !; ' ' #(** $ ' %"NN!d<GN!d !d !d    #LL!Z`a !Z !Z !Z  
 #/!S# # $%%    %: $&9-Mz#Z#Z#`#`#b#b #D*:;;;  )PVWWWW T"Iw"I"I"IJJKKK?@@@@ T"Iw"I"I"IJJKKK?@@@@@[m	A m	A m	A m	A m	A m	A m	A m	A m	A m	A m	A m	A m	A m	A m	A m	A m	A m	A^    N_NNOOO X X XEQVEEPTUUUOqvOOPPVWW ? ? ?>1>>NNN677Q>?s   BU ,?U +B5U!BS>;BKS>
L5LS>LS>9A2N,+S>,
Q6BQS>QA S>=AU>AU  UU UU UU 1X<WX/XXc                    	 ddl m} ddlm} ddlm} | t          j        d          } | st          	                    d           dS  |t          |                     }|                                5 }|                    |          }|                                }ddd           n# 1 swxY w Y   |+t          t                    j        }	t#          |	d	z            }t          |          }
|
                                s!t          	                    d
|            |dfS t'                      }|                    d|           |                    dd           |                    |          }|                                }||fS # t.          $ r(}t          	                    d|            Y d}~dS d}~ww xY w)a  
    Check current database schema version and latest available version.

    Args:
        database_url: SQLAlchemy database URL. If None, uses HINDSIGHT_API_DATABASE_URL env var.
        script_location: Path to alembic migrations directory. If None, uses default location.

    Returns:
        Tuple of (current_revision, head_revision)
        Returns (None, None) if unable to determine versions
    r   )MigrationContext)ScriptDirectory)r   NHINDSIGHT_API_DATABASE_URLz_Database URL not provided and HINDSIGHT_API_DATABASE_URL not set, cannot check migration statusNNrK   zScript location not found at r6   r;   r<   z"Unable to check migration status: )alembic.runtime.migrationrp   alembic.scriptrq   
sqlalchemyr   r<   r`   r@   rE   r   rU   	configureget_current_revisionr   rO   rP   rQ   rR   r   rB   from_configget_current_headr\   )r5   r6   rp   rq   r   ri   
connectioncontextcurrent_revrf   rg   rF   scripthead_revrG   s                  r   check_migration_statusr   o  s:   *>>>>>>222222,,,,,, 9%ABBL 	NNq   : |L99::^^ 	9&00<<G!6688K	9 	9 	9 	9 	9 	9 	9 	9 	9 	9 	9 	9 	9 	9 	9
 "x../K!+	"9::O?++!!## 	%NNL?LLMMM$$ hh##$5GGG##$4d;;; ,,[99**,,H$$   ?A??@@@zzzzzsJ   AF ,F 4*B*F *B..F 1B.2A3F &A&F 
F?F::F?
table_namerequired_dimension
vector_extc                 B   |                      t          d          ||d                                          }| t                              d| d           dS ||k    r"t                              d| d|            dS t                              d| d	| d
|            |                      t          d| d| d                                                    }|dk    r%t          d| d| d| d| d| d| d| d          t                              d| d| d|            |                      t          d| d| d| d                     |                      t          d| d| d| d                     |                                  |dk    r|dk    rt          d | d!          t          |          }t          ||          r/t          |          }t                              d"||||           dS |                      t          d#| d$| d%| d| d&t          |           d'                     t                              d(| d)| d*| d           |                                  t                              d+| d,|            dS )-a   
    Migrate the embedding column of a single table to the required dimension.

    - If dimensions match: no action needed
    - If dimensions differ and table is empty: ALTER COLUMN to new dimension
    - If dimensions differ and table has data: raise error with migration guidance
    a'  
            SELECT atttypmod
            FROM pg_attribute a
            JOIN pg_class c ON a.attrelid = c.oid
            JOIN pg_namespace n ON c.relnamespace = n.oid
            WHERE n.nspname = :schema
              AND c.relname = :table
              AND a.attname = 'embedding'
        )r(   tableNzNo embedding column found on z
, skippingzEmbedding dimension OK for : z Embedding dimension mismatch on z: database has z, model requires SELECT COUNT(*) FROM r:    WHERE embedding IS NOT NULLr   z'Cannot change embedding dimension from  to z table contains zb rows with embeddings. To change dimensions, you must either:
  1. Re-embed all data: DELETE FROM z%; then restart
  2. Use a model with z-dimensional embeddingsz	Altering z!.embedding column dimension from z
            DO $$
            DECLARE idx_name TEXT;
            BEGIN
                FOR idx_name IN
                    SELECT indexname FROM pg_indexes
                    WHERE schemaname = 'z)'
                      AND tablename = 'a  '
                      AND (indexdef LIKE '%hnsw%' OR indexdef LIKE '%vchordrq%' OR indexdef LIKE '%diskann%' OR indexdef LIKE '%scann%')
                      AND indexdef LIKE '%embedding%'
                LOOP
                    EXECUTE 'DROP INDEX IF EXISTS zF.' || idx_name;
                END LOOP;
            END $$;
        ALTER TABLE z$ ALTER COLUMN embedding TYPE vector(rL   r     Embedding dimension  exceeds pgvector HNSW index limit of 2000. Use an embedding model with <= 2000 dimensions, or switch to a vector extension that supports higher dimensions (e.g., pgvectorscale/DiskANN or AlloyDB ScaNN).zSkipping %s index recreation on %s: AlloyDB ScaNN AUTO indexes need at least %s populated embedding rows; table currently has %sz,
            CREATE INDEX IF NOT EXISTS idx__embedding_z
            ON z
            z	
        zCreated 
 index on z for zSuccessfully changed z.embedding dimension to )r    r	   rW   r@   rV   rA   r_   rX   r   r   r   rE   r   )	r   r   r   r   r   current_dim	row_count
index_typeminimum_rowss	            r   "_migrate_table_embedding_dimensionr     s    ,,  	 	 44  fhh  KZKKKLLL(((N:NNNNOOO
KKx:xxkxxdvxx   [[[[:[[[\\ fhh  1}}Jk J JGY J JJ J+4J J 4?J J BLJ J &1	J J J
 
 	
 KKnJnnnnZlnnooo 	LL  *5  )3  4?   	 	  $ 	LLoKoo*ooZlooopp   	KKMMM Z$6$=$=_#5 _ _ _
 
 	
 $J//J":y99 
-j995	
 	
 	
 	LL ,6 CM  (   
++   	 	   KKm:mmmmBTmmmnnnKKMMM
KK`
``L^``aaaaar   c                    |pd}t          t          |                     }|                                5 }|                    t	          d          d|i                                          }|s,t                              d| d           	 ddd           dS t          ||          }t          	                    d|            t          ||d||           t          ||d	||           ddd           dS # 1 swxY w Y   dS )
a  
    Ensure the embedding column dimension matches the model's dimension for all tables.

    Checks and adjusts memory_units.embedding and mental_models.embedding:
    - If dimensions match: no action needed
    - If dimensions differ and table is empty: ALTER COLUMN to new dimension
    - If dimensions differ and table has data: raise error with migration guidance

    Args:
        database_url: SQLAlchemy database URL
        required_dimension: The embedding dimension required by the model
        schema: Target PostgreSQL schema name (None for public)
        vector_extension: Configured vector extension ("pgvector", "vchord", "pgvectorscale", or "scann")

    Raises:
        RuntimeError: If dimension mismatch with existing data
    r8   z
                SELECT EXISTS (
                    SELECT 1 FROM information_schema.tables
                    WHERE table_schema = :schema AND table_name = 'memory_units'
                )
            r(   z-memory_units table does not exist in schema 'z', skipping dimension checkNzUsing vector extension: memory_unitsmental_models)r   r   rU   r    r	   rW   r@   rV   r   rA   r   )	r5   r   r(   r   r   ri   r   table_existsr   s	            r   ensure_embedding_dimensionr     s   . $HK<5566F			 oT||    {#
 
 &(( 	  	LLqqqqrrro o o o o o o o" .d4DEE
;z;;<<<*4nN`blmmm*4oOacmnnn+o o o o o o o o o o o o o o o o o os   AC;AC;;C?C?c                    |pd}t          t          |                     }|                                5 }t          ||          }t                              d|            g d}t          |          }g }	g }
|D ]\  }}|                    t          d          ||d          	                                }|s"t          
                    d| d| d           b|                    t          d	| d
| d                    	                                }|                    t          d          ||dd                                          }|s|dk    rrt          |          rc|                    t          d          ||d          	                                }|r)|dk    r#t          
                    d| d| d           Pt                              d| d           |	                    ||d|f           |d                                         }d|v rd}n7d|v rd}n0d|v rd}n)d|v rd}n"t                              d| d|            ||k    rbt                              d| d| d |            |	                    ||||f           |dk    r|dk    r|
                    |||f           It          
                    d!| d|            |dk    r*|dk    r$t!          ||           |                                 |	s+t          
                    d"|            	 ddd           dS |
rmd#                    d$ |
D                       }|
d         d%         }d&d'd(dd)                    ||          }t)          d*| d+| d,| d-| d.| d/| d0| d1          t                              d2|            |	D ]d\  }}}}t+          ||          r.t-          |          }t                              d3||||           F|rHt                              d4| d5|            |                    t          d6| d
|                      |d(k    rV|                    t          d7          ||d          	                                }|r|d8k    rt)          d9| d:| d;          t                              d<| d5|            |                    t          d=| d>| d
| d?t/          |           d@	                     |dk    r|dk    rt!          ||           f|                                 t                              dA|            ddd           dS # 1 swxY w Y   dS )Ba  
    Ensure the vector indexes match the configured vector extension.

    This function checks the current vector index type in the database
    and adjusts it if necessary:
    - If index type matches configured extension: no action needed
    - If they differ and tables are empty: drop old indexes, recreate with new type
    - If they differ and tables have data: raise error with migration guidance

    Args:
        database_url: SQLAlchemy database URL
        vector_extension: Configured vector extension ("pgvector", "vchord", "pgvectorscale", or "scann")
        schema: Target PostgreSQL schema name (None for public)

    Raises:
        RuntimeError: If extension mismatch with existing data
    r8   zTarget vector extension: ))r   idx_memory_units_embedding)	learningsidx_learnings_embedding)pinned_reflections idx_pinned_reflections_embedding
                    SELECT EXISTS (
                        SELECT 1 FROM information_schema.tables
                        WHERE table_schema = :schema AND table_name = :table_name
                    )
                r(   r   Table  does not exist in schema '', skippingr   r:   r   z
                    SELECT indexdef
                    FROM pg_indexes
                    WHERE schemaname = :schema
                      AND tablename = :table_name
                      AND indexname LIKE :index_pattern
                z%embedding%)r(   r   index_patternr   a"  
                            SELECT COUNT(*)
                            FROM pg_indexes
                            WHERE schemaname = :schema
                              AND tablename = :table_name
                              AND indexname LIKE 'idx_mu_emb_%'
                        r   zNo global embedding index on z, but uI    per-bank partial vector indexes exist — skipping global index creationzNo embedding index found for z, will create it if safeNscanndiskannvchordrqhnswzUnknown index type for r   zIndex type mismatch on z
: current=z	, target=zIndex type OK for z/All vector indexes match configured extension: , c                 &    g | ]\  }}}| d | dS (z rows) ).0r   count_s       r   
<listcomp>z+ensure_vector_extension.<locals>.<listcomp>  s/    #e#e#equ$<$<u$<$<$<#e#e#er      pgvectorscalevchordr   )r   r   r   r   z$Cannot change vector extension from r   %: the following tables contain data: zS. To change vector extension, you must either:
  1. Re-embed all data: DELETE FROM .memory_units; DELETE FROM z.learnings; DELETE FROM zm.pinned_reflections; then restart
  2. Use the current vector extension (set HINDSIGHT_API_VECTOR_EXTENSION='')zReconciling vector indexes for z~Skipping %s index creation on %s: AlloyDB ScaNN AUTO indexes need at least %s populated embedding rows; table currently has %s	Dropping r   zDROP INDEX IF EXISTS aX  
                        SELECT atttypmod
                        FROM pg_attribute a
                        JOIN pg_class c ON a.attrelid = c.oid
                        JOIN pg_namespace n ON c.relnamespace = n.oid
                        WHERE n.nspname = :schema AND c.relname = :table_name AND a.attname = 'embedding'
                    r   r   z on r   z	Creating z0
                    CREATE INDEX IF NOT EXISTS z
                    ON z
                    z
                z+Successfully reconciled vector indexes for )r   r   rU   r   r@   rA   r   r    r	   rW   rV   r[   r   rE   appendra   r'   rX   joingetr_   r   r   r   )r5   r   r(   r   ri   r   
target_exttables_to_checktarget_index_typemismatched_tablestables_with_datar   
index_namer   r   current_index_infoper_bank_index_countindexdefcurrent_index_type
table_listcurrent_ext_namecurrent_typer   	embed_dims                           r   ensure_vector_extensionr   L  sK   , $HK<5566F			 tPT-d4DEE
<
<<===
 
 
 /z::&5 R	" R	""J
<<    'jAA  fhh    ejee[eeefffc[cc:cccdd fhh 
 "&    'jS`aa	" 	" hjj  & //4PQ[4\4\/ ,0<<    $/jII	, 	, fhh ) , !0Dq0H0HhJ h hNb h h h   !czcccddd!((*j$	)RSSS)!,2244H(""%,""h&&%.""x''%/""8##%+""QQQxQQRRR "%666tjttDVttartt   "((*jBTV_)`aaaq==Z7%:%:$++ZDV,WXXXT*TT@RTTUUU((Z>-I-I1$DDDKKMMM ! 	LLW:WWXXXQtP tP tP tP tP tP tP tPV  	#e#eTd#e#e#effJ!1!!4Q!7 +$" 	   
 c$&899  s7I s sO` s s6@s s 8Cs s  +	s s EP	s s
 _os s s   	BjBBCCC?P 0	A 0	A;J
L)*:yAA 
5jAA=      WLLL
LLMMMT"T+"T"T
"T"TUUVVV Z'' LL     +*EE	 	 &((   T!1!1&ky k kj k k k   KKM$5MMMMNNNLL 0: # &0  (
33       W$$~)E)E-dK@@@N*NNOOOitP tP tP tP tP tP tP tP tP tP tP tP tP tP tP tP tP tPs   LU6H%U66U:=U:nativetext_search_extensionc                 .   |pd}t          t          |                     }|                                5 }ddg}|dk    rd}d}n|dk    rd}d}nd	}d
}g }	g }
|D ]}|                    t	          d          ||d                                          }|s"t                              d| d| d           _|                    t	          d          ||d                                          }|s7t          	                    d| d           |	
                    |ddf           |d         }|                    t	          d          ||d                                          }|r|d         nd}||k    }|r||k    nd}|r|st                              d| d| d| d| d| d           |	
                    |||f           |                    t	          d| d|                                                     }|dk    r|

                    ||f           t                              d| d | d!|            |	s+t                              d"|            	 ddd           dS |
rld#                    d$ |
D                       }|	d         d         }|d	k    rd%}n|dk    rd}n|dk    rd}nd&}t          d'| d(| d)| d*| d+| d,| d-          t                              d.|            |	D ]\  }}}|r]t                              d/| d0|            |                    t	          d1| d2|                    dd3           d4                     |rIt                              d/| d5|            |                    t	          d6| d| d7                     |dk    rt                              d8|            |                    t	          d6| d| d9                     t                              d:|            |                    t	          d;|                    dd3           d<| d| d=                     \|dk    rt                              d>|            |                    t	          d6| d| d?                     t                              d:|            |dk    rd@}ndA}|                    t	          d;|                    dd3           d<| d| dB| dC	                     t                              dD|            |dk    rdE}ndF}|                    t	          dG| d| dH| dI                     t                              dJ|            |                    t	          d;|                    dd3           d<| d| dK                     |                                 t                              dL|            ddd           dS # 1 swxY w Y   dS )Ma  
    Ensure the text search columns and indexes match the configured extension.

    This function checks the current search_vector column type and index type
    in the database and adjusts them if necessary:
    - If they match configured extension: no action needed
    - If they differ and tables are empty: drop old column/index, recreate with new type
    - If they differ and tables have data: raise error with migration guidance

    Args:
        database_url: SQLAlchemy database URL
        text_search_extension: Configured text search extension ("native" or "vchord")
        schema: Target PostgreSQL schema name (None for public)

    Raises:
        RuntimeError: If extension mismatch with existing data
    r8   r   reflectionsr   
bm25vectorbm25pg_textsearchr	   tsvectorginr   r   r   r   r   a  
                    SELECT data_type, udt_name
                    FROM information_schema.columns
                    WHERE table_schema = :schema
                      AND table_name = :table_name
                      AND column_name = 'search_vector'
                z"No search_vector column found for z, will create itNr
   av  
                    SELECT am.amname
                    FROM pg_indexes pi
                    JOIN pg_class c ON c.relname = pi.indexname
                    JOIN pg_am am ON am.oid = c.relam
                    WHERE pi.schemaname = :schema
                      AND pi.tablename = :table_name
                      AND pi.indexname LIKE '%text_search%'
                r   FzText search mismatch on z	: column=z (want z	), index=rL   r   r:   zText search OK for r   /z<All text search columns/indexes match configured extension: r   c                 $    g | ]\  }}| d | dS r   r   )r   r   r   s      r   r   z0ensure_text_search_extension.<locals>.<listcomp>  s-    #b#b#bu$<$<u$<$<$<#b#b#br   r   unknownz)Cannot change text search extension from r   r   zU. To change text search extension, you must either:
  1. Clear all data: DELETE FROM r   zp.reflections; then restart
  2. Use the current text search extension (set HINDSIGHT_API_TEXT_SEARCH_EXTENSION='r   z+Recreating text search columns/indexes for r   r   z.
                        DROP INDEX IF EXISTS z.idx_r   z!_text_search
                    z column on r   z$ DROP COLUMN IF EXISTS search_vectorzCreating bm25vector column on z1 ADD COLUMN search_vector bm25_catalog.bm25vectorzCreating BM25 index on z*
                        CREATE INDEX idx_z(_text_search
                        ON z^
                        USING bm25 (search_vector bm25_catalog.bm25_ops)
                    zCreating TEXT column on z ADD COLUMN search_vector TEXTz4(COALESCE(text, '') || ' ' || COALESCE(context, ''))z&(COALESCE(name, '') || ' ' || content)z$
                        USING bm25(zK)
                        WITH (text_config='english')
                    zCreating tsvector column on zJto_tsvector('english', COALESCE(text, '') || ' ' || COALESCE(context, ''))z<to_tsvector('english', COALESCE(name, '') || ' ' || content)z%
                        ALTER TABLE zh
                        ADD COLUMN search_vector tsvector
                        GENERATED ALWAYS AS (z) STORED
                    zCreating GIN index on zF
                        USING gin(search_vector)
                    z%Successfully migrated text search to )r   r   rU   r    r	   rW   r@   rV   r[   rE   r   rA   r   r_   r"   rX   )r5   r   r(   r   ri   r   r   target_column_typer   r   r   r   r   current_column_infocurrent_column_typer   r   column_matchesindex_matchesr   r   current_col_typecurrent_extcurrent_idx_type
index_exprgenerated_exprs                             r   ensure_text_search_extensionr     sn	   , $HK<5566F			 KUT 
 !H,,!- &"o55!' &!+ %) F	m F	mJ<<    'jAA  fhh    ejee[eeefff #',,    'jAA	# 	# hjj   ' `J```aaa!((*dD)ABBB #6a"8 "&    'jAA" " hjj  ;M!V!3A!6!6RV 14FFNGYd.2CCC_dM" m} mMz M M1M M:LM M/M M8IM M M  
 "((*6IK])^___ !LL.`k.`.`T^.`.`)a)abbiikk	q==$++Z,CDDDk:kkATkkWikkllll ! 	LLoXmoopppAKU KU KU KU KU KU KU KUF  	#b#bQa#b#b#bccJ03A6:--&!\11&!V++-'xK x xMb x x6@x x 5@x x  +	x x
 itx x x   	YBWYYZZZ>O M	 M	:J(*: P(8PPJPPQQQ .9 @J@R@RSVX[@\@\         rQ(8QQZQQRRRT"o"o"oz"o"o"oppqqq %00IZIIJJJsssjssstt  
 BjBBCCC *4*<*<S#*F*F ' *4        '/99CzCCDDDT"i"i"iz"i"i"ijjkkk BjBBCCC//!WJJ!IJ *4*<*<S#*F*F ' *4  %/        G:GGHHH//%qNN%cN %0 3=  /=       AZAABBB *4*<*<S#*F*F ' *4        	S<QSSTTTWKU KU KU KU KU KU KU KU KU KU KU KU KU KU KU KU KU KUs   H5X
7NX

XX)r   )N)NNNrs   )Nr   )r   N)r   N)2__doc__r-   loggingr<   	threadingrY   pathlibr   rK   r   alembic.configr   alembic.script.revisionr   rv   r   r   r	   _vector_indexr   r   r   r   r   r   r   db_urlr   r   utilsr   	getLogger__name__r@   rT   LockrC   rQ   r   r'   r1   r4   rH   rn   tupler   r   r   r   r   r   r   r   <module>r      s\   "   				                  ! ! ! ! ! ! 3 3 3 3 3 3 6 6 6 6 6 6 6 6 6 6                  0 / / / / / / / ( ( ( ( ( (		8	$	$   	  ; ;S ;# ; ; ; ;
S
 S S S S S S(	A 	A 	A 	A 	A 	A4Z 4Z3 4Z 4ZcTXj 4Zdh 4Z 4Z 4Z 4Zr #')-	Y? Y?Y?4ZY? $JY?  $J	Y?
 
Y? Y? Y? Y?z DH8 8*869Dj8
3:sTz!"8 8 8 8vmb
mbmb mb 	mb
 mb 
mb mb mb mbf &	/o /o/o/o $J/o 	/o
 
/o /o /o /oh 'MP MPMPMP $JMP 
	MP MP MP MPd "*dU dUdUdU $JdU 
	dU dU dU dU dU dUr   