-- Phase 1 follow-up: integrity constraints, FTS, GIN, partial FK, -- and updated_at + asset_types.schema_version triggers. -- Drizzle-kit can't express any of these from the TS schema, so they live here. -- --------------------------------------------------------------------------- -- 1. assets.current_container_kind XOR location FK -- --------------------------------------------------------------------------- ALTER TABLE "assets" ADD CONSTRAINT "assets_location_xor" CHECK ( (current_container_kind = 'project' AND current_project_id IS NOT NULL AND current_property_id IS NULL) OR (current_container_kind = 'property' AND current_property_id IS NOT NULL AND current_project_id IS NULL) ); --> statement-breakpoint ALTER TABLE "asset_location_history" ADD CONSTRAINT "alh_to_xor" CHECK ( (to_kind = 'project' AND to_project_id IS NOT NULL AND to_property_id IS NULL) OR (to_kind = 'property' AND to_property_id IS NOT NULL AND to_project_id IS NULL) ); --> statement-breakpoint -- --------------------------------------------------------------------------- -- 2. Self-referential FK on asset_types.parent_id -- --------------------------------------------------------------------------- ALTER TABLE "asset_types" ADD CONSTRAINT "asset_types_parent_id_fk" FOREIGN KEY ("parent_id") REFERENCES "asset_types"("id") ON DELETE SET NULL; --> statement-breakpoint -- --------------------------------------------------------------------------- -- 3. JSONB GIN index on assets.custom_fields -- --------------------------------------------------------------------------- CREATE INDEX "assets_custom_fields_gin" ON "assets" USING GIN ("custom_fields" jsonb_path_ops); --> statement-breakpoint -- --------------------------------------------------------------------------- -- 4. Full-text search on assets (tsvector + trigger + GIN) -- --------------------------------------------------------------------------- ALTER TABLE "assets" ALTER COLUMN "search_tsv" TYPE tsvector USING NULL::tsvector; --> statement-breakpoint CREATE OR REPLACE FUNCTION assets_tsv_trigger() RETURNS trigger AS $$ BEGIN NEW.search_tsv := setweight(to_tsvector('simple', coalesce(NEW.name, '')), 'A') || setweight(to_tsvector('simple', coalesce(NEW.tag, '')), 'B') || setweight(to_tsvector('simple', coalesce(NEW.serial_number, '')), 'B') || setweight(to_tsvector('simple', coalesce(NEW.manufacturer, '')), 'C') || setweight(to_tsvector('simple', coalesce(NEW.model, '')), 'C'); RETURN NEW; END $$ LANGUAGE plpgsql; --> statement-breakpoint CREATE TRIGGER assets_tsv_upd BEFORE INSERT OR UPDATE ON "assets" FOR EACH ROW EXECUTE FUNCTION assets_tsv_trigger(); --> statement-breakpoint CREATE INDEX "assets_search_tsv_gin" ON "assets" USING GIN ("search_tsv"); --> statement-breakpoint -- --------------------------------------------------------------------------- -- 5. Generic updated_at trigger, attached to every Phase-0/1 table that has it -- --------------------------------------------------------------------------- CREATE OR REPLACE FUNCTION set_updated_at() RETURNS trigger AS $$ BEGIN NEW.updated_at := now(); RETURN NEW; END $$ LANGUAGE plpgsql; --> statement-breakpoint CREATE TRIGGER companies_set_updated_at BEFORE UPDATE ON "companies" FOR EACH ROW EXECUTE FUNCTION set_updated_at(); --> statement-breakpoint CREATE TRIGGER users_set_updated_at BEFORE UPDATE ON "users" FOR EACH ROW EXECUTE FUNCTION set_updated_at(); --> statement-breakpoint CREATE TRIGGER properties_set_updated_at BEFORE UPDATE ON "properties" FOR EACH ROW EXECUTE FUNCTION set_updated_at(); --> statement-breakpoint CREATE TRIGGER asset_types_set_updated_at BEFORE UPDATE ON "asset_types" FOR EACH ROW EXECUTE FUNCTION set_updated_at(); --> statement-breakpoint CREATE TRIGGER asset_field_defs_set_updated_at BEFORE UPDATE ON "asset_field_defs" FOR EACH ROW EXECUTE FUNCTION set_updated_at(); --> statement-breakpoint CREATE TRIGGER assets_set_updated_at BEFORE UPDATE ON "assets" FOR EACH ROW EXECUTE FUNCTION set_updated_at(); --> statement-breakpoint -- --------------------------------------------------------------------------- -- 6. Bump asset_types.schema_version when its field defs change -- (cache key for the runtime Zod validator) -- --------------------------------------------------------------------------- CREATE OR REPLACE FUNCTION bump_asset_type_schema_version() RETURNS trigger AS $$ DECLARE target_id uuid; BEGIN IF TG_OP = 'DELETE' THEN target_id := OLD.asset_type_id; ELSE target_id := NEW.asset_type_id; END IF; UPDATE asset_types SET schema_version = schema_version + 1, updated_at = now() WHERE id = target_id; RETURN NULL; END $$ LANGUAGE plpgsql; --> statement-breakpoint CREATE TRIGGER asset_field_defs_bump_version AFTER INSERT OR UPDATE OR DELETE ON "asset_field_defs" FOR EACH ROW EXECUTE FUNCTION bump_asset_type_schema_version();