-- 说明:为自动入库(Supabase -> LightRAG)提供队列表与触发器/函数。 -- 编码:UTF-8 create table public.rag_index_sources ( id uuid primary key default gen_random_uuid(), workspace_id uuid not null references public.workspaces(id) on delete cascade, source_type text not null, source_id uuid not null, document_id uuid references public.documents(id) on delete set null, user_id uuid references public.profiles(id) on delete set null, status text not null default 'pending', attempts integer not null default 0, last_error text, source_updated_at timestamp with time zone, last_enqueued_at timestamp with time zone not null default timezone('utc', now()), last_processed_at timestamp with time zone, created_at timestamp with time zone not null default timezone('utc', now()), updated_at timestamp with time zone not null default timezone('utc', now()), constraint rag_index_sources_source_type_check check ( source_type = any (array['document'::text, 'mindmap'::text, 'media_asset'::text]) ), constraint rag_index_sources_status_check check ( status = any (array['pending'::text, 'processing'::text, 'completed'::text, 'failed'::text, 'skipped'::text]) ) ); create unique index rag_index_sources_unique_source on public.rag_index_sources (source_type, source_id); create index rag_index_sources_status_idx on public.rag_index_sources (status, last_enqueued_at); create index rag_index_sources_workspace_status_idx on public.rag_index_sources (workspace_id, status, last_enqueued_at desc); alter table public.rag_index_sources enable row level security; create policy "Select rag index within workspace" on public.rag_index_sources as permissive for select to public using ( exists ( select 1 from public.workspace_members wm where wm.workspace_id = rag_index_sources.workspace_id and wm.user_id = uid() ) ); create or replace function public.set_rag_index_sources_updated_at() returns trigger language plpgsql as $function$ begin new.updated_at := timezone('utc', now()); return new; end; $function$; create trigger set_rag_index_sources_updated_at before update on public.rag_index_sources for each row execute function public.set_rag_index_sources_updated_at(); create or replace function public.upsert_rag_index_source( p_workspace_id uuid, p_source_type text, p_source_id uuid, p_document_id uuid, p_user_id uuid, p_source_updated_at timestamp with time zone ) returns void language plpgsql security definer set search_path to 'public' as $function$ begin insert into public.rag_index_sources ( workspace_id, source_type, source_id, document_id, user_id, status, source_updated_at, last_enqueued_at, last_error ) values ( p_workspace_id, p_source_type, p_source_id, p_document_id, p_user_id, 'pending', p_source_updated_at, timezone('utc', now()), null ) on conflict (source_type, source_id) do update set workspace_id = excluded.workspace_id, document_id = excluded.document_id, user_id = excluded.user_id, status = 'pending', source_updated_at = excluded.source_updated_at, last_enqueued_at = timezone('utc', now()), last_error = null, updated_at = timezone('utc', now()); end; $function$; create or replace function public.enqueue_rag_for_document() returns trigger language plpgsql as $function$ begin perform public.upsert_rag_index_source( new.workspace_id, 'document', new.id, new.id, new.user_id, new.updated_at ); return new; end; $function$; create or replace function public.enqueue_rag_for_media_asset() returns trigger language plpgsql as $function$ declare v_user_id uuid; begin select user_id into v_user_id from public.documents where id = new.document_id; perform public.upsert_rag_index_source( new.workspace_id, 'media_asset', new.id, new.document_id, coalesce(new.created_by, v_user_id), new.updated_at ); return new; end; $function$; create or replace function public.enqueue_rag_for_mindmap_node() returns trigger language plpgsql as $function$ declare v_user_id uuid; begin select user_id into v_user_id from public.documents where id = new.document_id; perform public.upsert_rag_index_source( new.workspace_id, 'mindmap', new.mindmap_id, new.document_id, v_user_id, new.updated_at ); return new; end; $function$; create trigger enqueue_rag_for_document_insert after insert on public.documents for each row execute function public.enqueue_rag_for_document(); create trigger enqueue_rag_for_document_update after update on public.documents for each row when ( old.title is distinct from new.title or old.content is distinct from new.content or old.raw_text is distinct from new.raw_text or old.mindmap_data is distinct from new.mindmap_data or old.deleted_at is distinct from new.deleted_at ) execute function public.enqueue_rag_for_document(); create trigger enqueue_rag_for_media_asset_insert after insert on public.media_assets for each row execute function public.enqueue_rag_for_media_asset(); create trigger enqueue_rag_for_media_asset_update after update on public.media_assets for each row when ( old.file_url is distinct from new.file_url or old.storage_path is distinct from new.storage_path or old.bucket is distinct from new.bucket or old.ocr_text is distinct from new.ocr_text or old.ocr_status is distinct from new.ocr_status or old.mime_type is distinct from new.mime_type ) execute function public.enqueue_rag_for_media_asset(); create trigger enqueue_rag_for_mindmap_node_insert after insert on public.mindmap_nodes for each row execute function public.enqueue_rag_for_mindmap_node(); create trigger enqueue_rag_for_mindmap_node_update after update on public.mindmap_nodes for each row when ( old.data is distinct from new.data or old.parent_id is distinct from new.parent_id or old.block_id is distinct from new.block_id or old.order_index is distinct from new.order_index ) execute function public.enqueue_rag_for_mindmap_node(); create or replace function public.backfill_rag_index_sources() returns integer language plpgsql security definer set search_path to 'public' as $function$ declare v_count integer := 0; begin -- 仅允许 service_role 调用,避免被普通用户滥用造成全量重索引 if auth.role() <> 'service_role' then raise exception 'forbidden'; end if; insert into public.rag_index_sources ( workspace_id, source_type, source_id, document_id, user_id, status, source_updated_at, last_enqueued_at, last_error ) select d.workspace_id, 'document', d.id, d.id, d.user_id, 'pending', d.updated_at, timezone('utc', now()), null from public.documents d on conflict (source_type, source_id) do update set workspace_id = excluded.workspace_id, document_id = excluded.document_id, user_id = excluded.user_id, status = 'pending', source_updated_at = excluded.source_updated_at, last_enqueued_at = timezone('utc', now()), last_error = null, updated_at = timezone('utc', now()); get diagnostics v_count = row_count; return v_count; end; $function$; grant execute on function public.backfill_rag_index_sources() to service_role;