diff --git a/hindsight-api-slim/hindsight_api/alembic/versions/c1a2b3d4e5f6_enable_pg_trgm_and_entities_trgm_index.py b/hindsight-api-slim/hindsight_api/alembic/versions/c1a2b3d4e5f6_enable_pg_trgm_and_entities_trgm_index.py index db7a707d..5537a464 100644 --- a/hindsight-api-slim/hindsight_api/alembic/versions/c1a2b3d4e5f6_enable_pg_trgm_and_entities_trgm_index.py +++ b/hindsight-api-slim/hindsight_api/alembic/versions/c1a2b3d4e5f6_enable_pg_trgm_and_entities_trgm_index.py @@ -11,6 +11,7 @@ block; see migrations.py for how this is handled safely. from collections.abc import Sequence +import sqlalchemy as sa from alembic import context, op revision: str = "c1a2b3d4e5f6" @@ -25,9 +26,21 @@ def _get_schema_prefix() -> str: def upgrade() -> None: - # pg_trgm ships with every standard PostgreSQL installation as a contrib module. + # pg_trgm ships with most PostgreSQL installations as a contrib module. # It enables fast similarity lookups via GIN indexes, used for entity name matching. - op.execute("CREATE EXTENSION IF NOT EXISTS pg_trgm") + # On managed services (e.g. Azure Flexible Server), the extension may not be + # available or may require manual enablement. We gracefully skip the index + # creation if the extension cannot be loaded — the entity resolver will + # auto-detect and fall back to the "full" lookup strategy at runtime. See #626. + conn = op.get_bind() + try: + conn.execute(sa.text("CREATE EXTENSION IF NOT EXISTS pg_trgm")) + except Exception: + # Extension not available (managed Postgres, insufficient privileges, etc.) + # Roll back the failed statement and skip index creation. + conn.execute(sa.text("ROLLBACK")) + conn.execute(sa.text("BEGIN")) + return schema = _get_schema_prefix() # GIN index on canonical_name enables sub-millisecond trigram similarity queries diff --git a/hindsight-api-slim/hindsight_api/engine/entity_resolver.py b/hindsight-api-slim/hindsight_api/engine/entity_resolver.py index 6fc8f290..d1ed1fc7 100644 --- a/hindsight-api-slim/hindsight_api/engine/entity_resolver.py +++ b/hindsight-api-slim/hindsight_api/engine/entity_resolver.py @@ -75,6 +75,7 @@ class EntityResolver: """ self.pool = pool self.entity_lookup = entity_lookup + self._pg_trgm_checked = False # Keyed by asyncio task id so concurrent retain batches never mix their # pending updates. flush_pending_stats() pops only the calling task's items. self._pending_stats: dict[int, list[_EntityStat]] = {} @@ -202,6 +203,24 @@ class EntityResolver: taxonomy_lookup: set[str] | None = None, ) -> list[str]: if self.entity_lookup == "trigram": + # Auto-detect pg_trgm availability on first call and fall back to + # "full" strategy if the extension is not installed. See #626. + if not self._pg_trgm_checked: + self._pg_trgm_checked = True + has_trgm = await conn.fetchval( + "SELECT EXISTS(SELECT 1 FROM pg_extension WHERE extname = 'pg_trgm')" + ) + if not has_trgm: + logger.warning( + "pg_trgm extension is not available — falling back to 'full' " + "entity lookup strategy. Install pg_trgm for faster entity " + "resolution on large banks. See: " + "https://github.com/vectorize-io/hindsight/issues/626" + ) + self.entity_lookup = "full" + return await self._resolve_entities_batch_full( + conn, bank_id, entities_data, unit_event_date + ) return await self._resolve_entities_batch_trigram(conn, bank_id, entities_data, unit_event_date) return await self._resolve_entities_batch_full(conn, bank_id, entities_data, unit_event_date)