Files
ThothII/harness/scripts/create_vector_writer_rpc.sql
T

183 lines
7.2 KiB
PL/PgSQL

-- RPC writer controllate per pgvector via Supabase/PostgREST.
-- Eseguire sul database Supabase centrale come owner dello schema `vectors`.
-- Prerequisiti:
-- - schema `vectors` con tabelle schema_records/evidence/memory
-- - colonne: record_key text unique, kind text, content_hash text, metadata jsonb,
-- embedding vector(N), indexed_at timestamptz
-- - ruolo/key PostgREST dedicato `vector_writer`
--
-- La key writer deve ricevere solo EXECUTE su queste funzioni, non privilegi raw di DELETE
-- sulle tabelle. Cleanup/rebuild restano via terminale sul server.
create or replace function public._assert_vector_write_table(table_name text, kinds text[])
returns void
language plpgsql
stable
as $$
begin
if table_name not in ('schema_records', 'evidence', 'memory') then
raise sqlstate 'PGRST' using
message = json_build_object('message', 'Unsupported vector table')::text,
detail = json_build_object('status', 400)::text;
end if;
if table_name = 'schema_records'
and exists (select 1 from unnest(kinds) k where k not in ('schema_table', 'schema_column')) then
raise sqlstate 'PGRST' using
message = json_build_object('message', 'Invalid kind for schema_records')::text,
detail = json_build_object('status', 400)::text;
elsif table_name = 'evidence'
and exists (select 1 from unnest(kinds) k where k <> 'evidence') then
raise sqlstate 'PGRST' using
message = json_build_object('message', 'Invalid kind for evidence')::text,
detail = json_build_object('status', 400)::text;
elsif table_name = 'memory'
and exists (select 1 from unnest(kinds) k where k <> 'memory') then
raise sqlstate 'PGRST' using
message = json_build_object('message', 'Invalid kind for memory')::text,
detail = json_build_object('status', 400)::text;
end if;
end;
$$;
drop function if exists public.list_evidence_generations(text, text);
create or replace function public.list_evidence_generations(
table_name text, kind text, workspace_id text
)
returns table(generation text)
language plpgsql
security definer
set search_path = public, vectors, extensions
as $$
begin
if table_name <> 'evidence' or kind <> 'evidence' or workspace_id !~ '^[a-z][a-z0-9_-]{0,63}$' then
raise exception 'only exact Evidence generations may be listed';
end if;
return query
select distinct e.metadata->>'vector_generation'
from vectors.evidence e
where e.kind = 'evidence'
and e.metadata->>'vector_generation' ~ '^gen:[0-9a-f]{32}$'
and e.metadata->>'workspace_id' = workspace_id
order by 1;
end;
$$;
create or replace function public.existing_vector_hashes(table_name text, kinds text[])
returns table(record_key text, content_hash text)
language plpgsql
security definer
set search_path = public, vectors, extensions
as $$
begin
perform public._assert_vector_write_table(table_name, kinds);
return query execute format(
'select record_key, content_hash from vectors.%I where kind = any ($1)',
table_name
) using kinds;
end;
$$;
create or replace function public.upsert_vector_records(table_name text, rows jsonb)
returns jsonb
language plpgsql
security definer
set search_path = public, vectors, extensions
as $$
declare
r jsonb;
affected integer := 0;
kinds text[];
begin
if jsonb_typeof(rows) <> 'array' then
raise sqlstate 'PGRST' using
message = json_build_object('message', 'rows must be a JSON array')::text,
detail = json_build_object('status', 400)::text;
end if;
select coalesce(array_agg(distinct value->>'kind'), array[]::text[])
into kinds
from jsonb_array_elements(rows) value;
perform public._assert_vector_write_table(table_name, kinds);
for r in select * from jsonb_array_elements(rows) loop
execute format(
'insert into vectors.%I (record_key, kind, content_hash, metadata, embedding)
values ($1, $2, $3, $4::jsonb, $5::vector)
on conflict (record_key) do update set
kind = excluded.kind,
content_hash = excluded.content_hash,
metadata = excluded.metadata,
embedding = excluded.embedding,
indexed_at = now()',
table_name
) using
r->>'record_key',
r->>'kind',
r->>'content_hash',
coalesce(r->'metadata', '{}'::jsonb),
('[' || (select string_agg(value::text, ',') from jsonb_array_elements(r->'embedding')) || ']');
affected := affected + 1;
end loop;
return jsonb_build_object('upserted', affected);
end;
$$;
drop function if exists public.delete_vector_generation(text, text, text);
create or replace function public.delete_vector_generation(
table_name text, kind text, generation text, workspace_id text
)
returns jsonb
language plpgsql
security definer
set search_path = public, vectors, extensions
as $$
declare affected integer;
begin
if table_name <> 'evidence' or kind <> 'evidence' or generation !~ '^gen:[0-9a-f]{32}$'
or workspace_id !~ '^[a-z][a-z0-9_-]{0,63}$' then
raise exception 'only an exact Evidence generation may be deleted';
end if;
delete from vectors.evidence e
where e.kind = 'evidence' and e.metadata->>'vector_generation' = generation
and e.metadata->>'workspace_id' = workspace_id;
get diagnostics affected = row_count;
return jsonb_build_object('deleted', affected);
end;
$$;
revoke all on function public._assert_vector_write_table(text, text[]) from public;
revoke all on function public.existing_vector_hashes(text, text[]) from public;
revoke all on function public.upsert_vector_records(text, jsonb) from public;
revoke all on function public.delete_vector_generation(text, text, text, text) from public;
revoke all on function public.list_evidence_generations(text, text, text) from public;
-- Su alcuni progetti Supabase le funzioni in `public` ricevono grant automatici: revoca
-- esplicitamente dai ruoli client generici, poi abilita solo il writer dedicato.
do $$
begin
if exists (select 1 from pg_roles where rolname = 'anon') then
revoke all on function public.existing_vector_hashes(text, text[]) from anon;
revoke all on function public.upsert_vector_records(text, jsonb) from anon;
revoke all on function public.delete_vector_generation(text, text, text, text) from anon;
revoke all on function public.list_evidence_generations(text, text, text) from anon;
end if;
if exists (select 1 from pg_roles where rolname = 'authenticated') then
revoke all on function public.existing_vector_hashes(text, text[]) from authenticated;
revoke all on function public.upsert_vector_records(text, jsonb) from authenticated;
revoke all on function public.delete_vector_generation(text, text, text, text) from authenticated;
revoke all on function public.list_evidence_generations(text, text, text) from authenticated;
end if;
if exists (select 1 from pg_roles where rolname = 'vector_writer') then
grant execute on function public.existing_vector_hashes(text, text[]) to vector_writer;
grant execute on function public.upsert_vector_records(text, jsonb) to vector_writer;
grant execute on function public.delete_vector_generation(text, text, text, text) to vector_writer;
grant execute on function public.list_evidence_generations(text, text, text) to vector_writer;
end if;
end $$;
notify pgrst, 'reload schema';