-- Full-text search infrastructure for /api/cauta and /achizitii/cauta. -- Uses 'simple' config + unaccent for diacritic-insensitive matching, since RO -- doesn't have a built-in PG text search config and we don't want stemming bias. -- -- Idempotent: safe to re-run. BEGIN; -- Ensure unaccent extension CREATE EXTENSION IF NOT EXISTS unaccent; -- Wrap unaccent as IMMUTABLE so it can be used in expression indexes / generated cols. -- Safe because we don't reload the unaccent dictionary at runtime. CREATE OR REPLACE FUNCTION seap.immutable_unaccent(text) RETURNS text LANGUAGE sql IMMUTABLE PARALLEL SAFE STRICT AS $$ SELECT public.unaccent('public.unaccent', $1) $$; -- Plain (non-generated) tsvector column populated by trigger. ALTER TABLE seap.announcements ADD COLUMN IF NOT EXISTS search_tsv tsvector; CREATE OR REPLACE FUNCTION seap.update_search_tsv() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN NEW.search_tsv := setweight(to_tsvector('simple', seap.immutable_unaccent(coalesce(NEW.title, ''))), 'A') || setweight(to_tsvector('simple', seap.immutable_unaccent(coalesce(NEW.description, ''))), 'B') || setweight(to_tsvector('simple', seap.immutable_unaccent(coalesce(NEW.authority_name, ''))), 'C') || setweight(to_tsvector('simple', seap.immutable_unaccent(coalesce(NEW.supplier_name, ''))), 'C') || setweight(to_tsvector('simple', seap.immutable_unaccent(coalesce(NEW.cpv_name_ro, ''))), 'D') || setweight(to_tsvector('simple', seap.immutable_unaccent(coalesce(NEW.cpv_name, ''))), 'D'); RETURN NEW; END $$; DROP TRIGGER IF EXISTS trg_announcements_search_tsv ON seap.announcements; CREATE TRIGGER trg_announcements_search_tsv BEFORE INSERT OR UPDATE OF title, description, authority_name, supplier_name, cpv_name_ro, cpv_name ON seap.announcements FOR EACH ROW EXECUTE FUNCTION seap.update_search_tsv(); CREATE INDEX IF NOT EXISTS idx_ann_search_tsv ON seap.announcements USING gin(search_tsv); -- Title-only trgm for "starts-with" or substring autocompletes CREATE INDEX IF NOT EXISTS idx_ann_title_trgm ON seap.announcements USING gin(title gin_trgm_ops); COMMIT; -- Backfill existing rows (run outside the transaction). Long-running on 642K -- rows but does NOT block reads. UPDATE seap.announcements SET search_tsv = setweight(to_tsvector('simple', seap.immutable_unaccent(coalesce(title, ''))), 'A') || setweight(to_tsvector('simple', seap.immutable_unaccent(coalesce(description, ''))), 'B') || setweight(to_tsvector('simple', seap.immutable_unaccent(coalesce(authority_name, ''))), 'C') || setweight(to_tsvector('simple', seap.immutable_unaccent(coalesce(supplier_name, ''))), 'C') || setweight(to_tsvector('simple', seap.immutable_unaccent(coalesce(cpv_name_ro, ''))), 'D') || setweight(to_tsvector('simple', seap.immutable_unaccent(coalesce(cpv_name, ''))), 'D') WHERE search_tsv IS NULL;