Spaces:
Running
Running
| BEGIN; | |
| DO $$ | |
| DECLARE | |
| actual_type TEXT; | |
| BEGIN | |
| SELECT format_type(attribute.atttypid, attribute.atttypmod) | |
| INTO actual_type | |
| FROM pg_attribute attribute | |
| WHERE attribute.attrelid = 'public.post_embeddings'::regclass | |
| AND attribute.attname = 'embedding' | |
| AND NOT attribute.attisdropped; | |
| IF actual_type IS DISTINCT FROM 'vector(300)' THEN | |
| RAISE EXCEPTION 'post_embeddings.embedding debe ser vector(300), actual=%', actual_type; | |
| END IF; | |
| IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'nlp_owner') | |
| OR NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'nlp_reader') | |
| OR NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'nlp_writer') THEN | |
| RAISE EXCEPTION 'se requieren los roles nlp_owner, nlp_reader y nlp_writer'; | |
| END IF; | |
| END $$; | |
| CREATE OR REPLACE FUNCTION public.upsert_post_embedding( | |
| p_external_id TEXT, | |
| p_document TEXT, | |
| p_metadata JSONB, | |
| p_embedding VECTOR(300), | |
| p_content_hash TEXT, | |
| p_embedding_model TEXT, | |
| p_embedding_version TEXT, | |
| p_is_active BOOLEAN, | |
| p_author_type TEXT, | |
| p_author_id TEXT, | |
| p_published_at TIMESTAMPTZ, | |
| p_source_version BIGINT | |
| ) RETURNS VOID | |
| LANGUAGE plpgsql | |
| SECURITY DEFINER | |
| SET search_path = public | |
| AS $$ | |
| DECLARE | |
| current_version BIGINT; | |
| current_hash TEXT; | |
| BEGIN | |
| IF p_source_version IS NULL OR p_source_version < 0 THEN | |
| RAISE EXCEPTION 'source_version es obligatorio y no negativo para %', p_external_id; | |
| END IF; | |
| SELECT source_version, content_hash | |
| INTO current_version, current_hash | |
| FROM public.post_embeddings | |
| WHERE external_id = p_external_id; | |
| IF FOUND AND current_version IS NOT NULL THEN | |
| IF current_version > p_source_version THEN | |
| RETURN; | |
| END IF; | |
| IF current_version = p_source_version THEN | |
| IF current_hash = p_content_hash THEN | |
| RETURN; | |
| END IF; | |
| RAISE EXCEPTION 'conflicto: misma source_version con contenido distinto para %', p_external_id; | |
| END IF; | |
| END IF; | |
| IF EXISTS ( | |
| SELECT 1 | |
| FROM public.post_embedding_tombstones tombstone | |
| WHERE tombstone.post_id = p_external_id | |
| AND tombstone.source_version >= p_source_version | |
| ) THEN | |
| RETURN; | |
| END IF; | |
| DELETE FROM public.post_embedding_tombstones | |
| WHERE post_id = p_external_id AND source_version < p_source_version; | |
| INSERT INTO public.post_embeddings ( | |
| external_id, document, metadata, embedding, content_hash, | |
| embedding_model, embedding_version, is_active, author_type, | |
| author_id, published_at, source_version, indexed_at, updated_at | |
| ) VALUES ( | |
| p_external_id, p_document, p_metadata, p_embedding, p_content_hash, | |
| p_embedding_model, p_embedding_version, p_is_active, p_author_type, | |
| p_author_id, p_published_at, p_source_version, now(), now() | |
| ) | |
| ON CONFLICT (external_id) DO UPDATE SET | |
| document = EXCLUDED.document, | |
| metadata = EXCLUDED.metadata, | |
| embedding = EXCLUDED.embedding, | |
| content_hash = EXCLUDED.content_hash, | |
| embedding_model = EXCLUDED.embedding_model, | |
| embedding_version = EXCLUDED.embedding_version, | |
| is_active = EXCLUDED.is_active, | |
| author_type = EXCLUDED.author_type, | |
| author_id = EXCLUDED.author_id, | |
| published_at = EXCLUDED.published_at, | |
| source_version = EXCLUDED.source_version, | |
| indexed_at = now(), | |
| updated_at = now() | |
| WHERE post_embeddings.source_version IS NULL | |
| OR EXCLUDED.source_version > post_embeddings.source_version; | |
| END; | |
| $$; | |
| CREATE OR REPLACE FUNCTION public.deactivate_post_embedding( | |
| p_external_id TEXT, | |
| p_source_version BIGINT | |
| ) RETURNS VOID | |
| LANGUAGE plpgsql | |
| SECURITY DEFINER | |
| SET search_path = public | |
| AS $$ | |
| BEGIN | |
| IF p_source_version IS NULL OR p_source_version < 0 THEN | |
| RAISE EXCEPTION 'source_version es obligatorio y no negativo para %', p_external_id; | |
| END IF; | |
| INSERT INTO public.post_embedding_tombstones (post_id, source_version, deleted_at) | |
| VALUES (p_external_id, p_source_version, now()) | |
| ON CONFLICT (post_id) DO UPDATE SET | |
| source_version = GREATEST(post_embedding_tombstones.source_version, EXCLUDED.source_version), | |
| deleted_at = CASE | |
| WHEN EXCLUDED.source_version >= post_embedding_tombstones.source_version THEN now() | |
| ELSE post_embedding_tombstones.deleted_at | |
| END; | |
| UPDATE public.post_embeddings | |
| SET is_active = FALSE, | |
| source_version = GREATEST(COALESCE(source_version, 0), p_source_version), | |
| indexed_at = now(), | |
| updated_at = now() | |
| WHERE external_id = p_external_id | |
| AND (source_version IS NULL OR p_source_version >= source_version); | |
| END; | |
| $$; | |
| CREATE OR REPLACE FUNCTION public.get_post_content_hashes(p_external_ids TEXT[]) | |
| RETURNS TABLE (external_id TEXT, content_hash TEXT) | |
| LANGUAGE sql | |
| STABLE | |
| SECURITY DEFINER | |
| SET search_path = public | |
| AS $$ | |
| SELECT post.external_id, post.content_hash | |
| FROM public.post_embeddings post | |
| WHERE post.external_id = ANY(p_external_ids) | |
| AND post.is_active = TRUE | |
| AND NOT EXISTS ( | |
| SELECT 1 FROM public.post_embedding_tombstones tombstone | |
| WHERE tombstone.post_id = post.external_id | |
| AND tombstone.source_version >= COALESCE(post.source_version, 0) | |
| ); | |
| $$; | |
| CREATE OR REPLACE FUNCTION public.get_post_feed_features( | |
| p_user_id TEXT, | |
| p_post_ids TEXT[] | |
| ) RETURNS TABLE ( | |
| external_id TEXT, | |
| semantic_similarity DOUBLE PRECISION, | |
| cluster_id INTEGER, | |
| cluster_run_id TEXT, | |
| cold_start BOOLEAN, | |
| post_embedding TEXT | |
| ) | |
| LANGUAGE sql | |
| STABLE | |
| SECURITY DEFINER | |
| SET search_path = public | |
| AS $$ | |
| WITH requested AS ( | |
| SELECT post_id, ordinality | |
| FROM unnest(p_post_ids) WITH ORDINALITY AS ids(post_id, ordinality) | |
| ), active_run AS ( | |
| SELECT id FROM public.post_cluster_runs | |
| WHERE status = 'active' | |
| ORDER BY activated_at DESC NULLS LAST | |
| LIMIT 1 | |
| ), profile AS ( | |
| SELECT embedding FROM public.user_interest_embeddings WHERE user_id = p_user_id | |
| ) | |
| SELECT requested.post_id, | |
| CASE WHEN profile.embedding IS NULL OR post.embedding IS NULL THEN 0.0 | |
| ELSE 1 - (post.embedding <=> profile.embedding) END, | |
| membership.cluster_id, | |
| active_run.id::text, | |
| (profile.embedding IS NULL OR post.embedding IS NULL), | |
| post.embedding::text | |
| FROM requested | |
| LEFT JOIN public.post_embeddings post | |
| ON post.external_id = requested.post_id AND post.is_active = TRUE | |
| LEFT JOIN profile ON TRUE | |
| LEFT JOIN active_run ON TRUE | |
| LEFT JOIN public.post_cluster_memberships membership | |
| ON membership.post_id = post.external_id AND membership.run_id = active_run.id | |
| ORDER BY requested.ordinality; | |
| $$; | |
| CREATE OR REPLACE FUNCTION public.activate_post_cluster_run(p_run_id UUID) | |
| RETURNS VOID | |
| LANGUAGE plpgsql | |
| SECURITY DEFINER | |
| SET search_path = public | |
| AS $$ | |
| DECLARE | |
| expected_k INTEGER; | |
| expected_sample_size INTEGER; | |
| cluster_count INTEGER; | |
| membership_count INTEGER; | |
| declared_size INTEGER; | |
| BEGIN | |
| PERFORM pg_advisory_xact_lock(hashtext('frimeet-post-cluster-activation')); | |
| SELECT k, sample_size INTO expected_k, expected_sample_size | |
| FROM public.post_cluster_runs | |
| WHERE id = p_run_id AND status IN ('validated', 'superseded', 'active') | |
| FOR UPDATE; | |
| IF NOT FOUND THEN | |
| RAISE EXCEPTION 'cluster run % no existe o no es activable', p_run_id; | |
| END IF; | |
| SELECT count(*), COALESCE(sum(size), 0) | |
| INTO cluster_count, declared_size | |
| FROM public.post_clusters WHERE run_id = p_run_id; | |
| SELECT count(*) INTO membership_count | |
| FROM public.post_cluster_memberships WHERE run_id = p_run_id; | |
| IF cluster_count <> expected_k | |
| OR membership_count <> expected_sample_size | |
| OR declared_size <> expected_sample_size THEN | |
| RAISE EXCEPTION 'cluster run % tiene artefactos incompletos: clusters=%/% memberships=%/% size=%/%', | |
| p_run_id, cluster_count, expected_k, membership_count, | |
| expected_sample_size, declared_size, expected_sample_size; | |
| END IF; | |
| IF EXISTS (SELECT 1 FROM public.post_cluster_runs WHERE id = p_run_id AND status = 'active') THEN | |
| RETURN; | |
| END IF; | |
| UPDATE public.post_cluster_runs SET status = 'superseded' WHERE status = 'active'; | |
| UPDATE public.post_cluster_runs | |
| SET status = 'active', activated_at = now() | |
| WHERE id = p_run_id; | |
| END; | |
| $$; | |
| ALTER FUNCTION public.upsert_post_embedding(text, text, jsonb, vector, text, text, text, boolean, text, text, timestamptz, bigint) OWNER TO nlp_owner; | |
| ALTER FUNCTION public.deactivate_post_embedding(text, bigint) OWNER TO nlp_owner; | |
| ALTER FUNCTION public.get_post_content_hashes(text[]) OWNER TO nlp_owner; | |
| ALTER FUNCTION public.get_post_feed_features(text, text[]) OWNER TO nlp_owner; | |
| ALTER FUNCTION public.activate_post_cluster_run(uuid) OWNER TO nlp_owner; | |
| REVOKE ALL ON FUNCTION public.upsert_post_embedding(text, text, jsonb, vector, text, text, text, boolean, text, text, timestamptz, bigint) FROM PUBLIC; | |
| REVOKE ALL ON FUNCTION public.deactivate_post_embedding(text, bigint) FROM PUBLIC; | |
| REVOKE ALL ON FUNCTION public.get_post_content_hashes(text[]) FROM PUBLIC; | |
| REVOKE ALL ON FUNCTION public.get_post_feed_features(text, text[]) FROM PUBLIC; | |
| REVOKE ALL ON FUNCTION public.activate_post_cluster_run(uuid) FROM PUBLIC; | |
| GRANT EXECUTE ON FUNCTION public.get_post_feed_features(text, text[]) TO nlp_reader; | |
| GRANT EXECUTE ON FUNCTION public.upsert_post_embedding(text, text, jsonb, vector, text, text, text, boolean, text, text, timestamptz, bigint) TO nlp_writer; | |
| GRANT EXECUTE ON FUNCTION public.deactivate_post_embedding(text, bigint) TO nlp_writer; | |
| GRANT EXECUTE ON FUNCTION public.get_post_content_hashes(text[]) TO nlp_writer; | |
| GRANT EXECUTE ON FUNCTION public.activate_post_cluster_run(uuid) TO nlp_writer; | |
| COMMIT; | |