121 lines
4.6 KiB
PL/PgSQL
121 lines
4.6 KiB
PL/PgSQL
-- RPC reader per pgvector via Supabase/PostgREST (spec D11, §5.4).
|
|
-- 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_reader` (lettura: search_similar, list_tables)
|
|
--
|
|
-- A differenza del writer RPC (che era gia' nel repo), i reader RPC vivevano lato server
|
|
-- in Supabase e non erano mai stati versionati. Li si riporta qui per completezza del
|
|
-- modello dual-key: la key reader riceve solo EXECUTE su queste due funzioni, niente
|
|
-- SELECT raw sulle tabelle. Il cleanup/rebuild resta solo-server (vedi writer RPC).
|
|
|
|
create or replace function public._assert_vector_read_table(table_name 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;
|
|
end;
|
|
$$;
|
|
|
|
create or replace function public.search_similar(
|
|
table_name text,
|
|
query_embedding vector,
|
|
limit_count integer,
|
|
kinds text[] default null,
|
|
metadata_filter jsonb default null
|
|
)
|
|
returns table(id bigint, similarity real, metadata jsonb)
|
|
language plpgsql
|
|
security definer
|
|
set search_path = public, vectors, extensions
|
|
as $$
|
|
begin
|
|
perform public._assert_vector_read_table(table_name);
|
|
if metadata_filter is not null and (
|
|
table_name <> 'evidence'
|
|
or not (metadata_filter ? 'vector_generation')
|
|
or not (metadata_filter ? 'document_ids')
|
|
or not (metadata_filter ? 'workspace_id')
|
|
or jsonb_object_length(metadata_filter) <> 3
|
|
or jsonb_typeof(metadata_filter->'document_ids') <> 'array'
|
|
) then
|
|
raise exception 'invalid Evidence metadata filter';
|
|
end if;
|
|
return query execute format(
|
|
'select t.id,
|
|
(1 - (t.embedding <=> $1))::real as similarity,
|
|
t.metadata
|
|
from vectors.%I t
|
|
where ($3 is null or t.kind = any ($3))
|
|
and ($4 is null or (
|
|
t.metadata->>''vector_generation'' = $4->>''vector_generation''
|
|
and t.metadata->>''workspace_id'' = $4->>''workspace_id''
|
|
and t.metadata->>''document_id'' in (
|
|
select jsonb_array_elements_text($4->''document_ids'')
|
|
)
|
|
))
|
|
order by t.embedding <=> $1
|
|
limit $2',
|
|
table_name
|
|
) using query_embedding, limit_count, kinds, metadata_filter;
|
|
end;
|
|
$$;
|
|
|
|
create or replace function public.list_tables()
|
|
returns table(table_name text, vector_dimensions integer, record_count bigint)
|
|
language plpgsql
|
|
security definer
|
|
set search_path = public, vectors, extensions
|
|
as $$
|
|
begin
|
|
return query
|
|
select c.relname::text as table_name,
|
|
-- dimensione ricavata dalla colonna embedding (vector(N)) se presente
|
|
coalesce(
|
|
(select (regexp_matches(format_type(a.atttypid, a.atttypmod), 'vector\((\d+)\)'))[1])::int,
|
|
0
|
|
) as vector_dimensions,
|
|
c.reltuples::bigint as record_count
|
|
from pg_class c
|
|
join pg_namespace n on n.oid = c.relnamespace
|
|
left join pg_attribute a on a.attrelid = c.oid and a.attname = 'embedding'
|
|
where n.nspname = 'vectors'
|
|
and c.relkind in ('r', 'p')
|
|
and c.relname in ('schema_records', 'evidence', 'memory')
|
|
order by c.relname;
|
|
end;
|
|
$$;
|
|
|
|
revoke all on function public._assert_vector_read_table(text) from public;
|
|
revoke all on function public.search_similar(text, vector, integer, text[], jsonb) from public;
|
|
revoke all on function public.list_tables() from public;
|
|
|
|
-- Revoca dai ruoli client generici, poi abilita solo il reader dedicato
|
|
-- (e il writer, che usa existing_vector_hashes per l'upsert incrementale).
|
|
do $$
|
|
begin
|
|
if exists (select 1 from pg_roles where rolname = 'anon') then
|
|
revoke all on function public.search_similar(text, vector, integer, text[], jsonb) from anon;
|
|
revoke all on function public.list_tables() from anon;
|
|
end if;
|
|
if exists (select 1 from pg_roles where rolname = 'authenticated') then
|
|
revoke all on function public.search_similar(text, vector, integer, text[], jsonb) from authenticated;
|
|
revoke all on function public.list_tables() from authenticated;
|
|
end if;
|
|
if exists (select 1 from pg_roles where rolname = 'vector_reader') then
|
|
grant execute on function public.search_similar(text, vector, integer, text[], jsonb)
|
|
to vector_reader;
|
|
grant execute on function public.list_tables() to vector_reader;
|
|
end if;
|
|
end $$;
|
|
|
|
notify pgrst, 'reload schema';
|