create table if not exists public.document_tables ( id uuid primary key default gen_random_uuid(), workspace_id uuid not null references public.workspaces(id) on delete cascade, document_id uuid not null references public.documents(id) on delete cascade, title text not null default '未命名表格', schema jsonb not null default '{}'::jsonb, view_preferences jsonb not null default '{}'::jsonb, is_archived boolean not null default false, snapshot jsonb, last_synced_at timestamptz, created_by uuid references public.profiles(id) on delete set null, updated_by uuid references public.profiles(id) on delete set null, created_at timestamptz not null default timezone('utc', now()), updated_at timestamptz not null default timezone('utc', now()) ); create index if not exists document_tables_workspace_idx on public.document_tables (workspace_id, updated_at desc); create index if not exists document_tables_document_idx on public.document_tables (document_id, updated_at desc); create index if not exists document_tables_archived_idx on public.document_tables (workspace_id, is_archived, updated_at desc); create or replace function public.set_document_tables_updated_at() returns trigger language plpgsql security invoker as $$ begin new.updated_at := timezone('utc', now()); return new; end; $$; drop trigger if exists set_document_tables_updated_at on public.document_tables; create trigger set_document_tables_updated_at before update on public.document_tables for each row execute procedure public.set_document_tables_updated_at(); create or replace function public.ensure_document_table_workspace() returns trigger language plpgsql security invoker as $$ declare v_workspace uuid; begin select workspace_id into v_workspace from public.documents where id = new.document_id; if not found then raise exception 'Document % not found when creating document table', new.document_id; end if; if new.workspace_id is null then new.workspace_id := v_workspace; elsif new.workspace_id <> v_workspace then raise exception 'Workspace mismatch between document table and document %', new.document_id; end if; return new; end; $$; drop trigger if exists ensure_document_table_workspace on public.document_tables; create trigger ensure_document_table_workspace before insert or update on public.document_tables for each row execute procedure public.ensure_document_table_workspace(); alter table public.document_tables enable row level security; drop policy if exists "Select tables within workspace" on public.document_tables; create policy "Select tables within workspace" on public.document_tables for select using ( exists ( select 1 from public.workspace_members wm where wm.workspace_id = document_tables.workspace_id and wm.user_id = auth.uid() ) ); drop policy if exists "Insert tables within workspace" on public.document_tables; create policy "Insert tables within workspace" on public.document_tables for insert with check ( exists ( select 1 from public.workspace_members wm where wm.workspace_id = document_tables.workspace_id and wm.user_id = auth.uid() ) ); drop policy if exists "Update tables within workspace" on public.document_tables; create policy "Update tables within workspace" on public.document_tables for update using ( exists ( select 1 from public.workspace_members wm where wm.workspace_id = document_tables.workspace_id and wm.user_id = auth.uid() ) ) with check ( exists ( select 1 from public.workspace_members wm where wm.workspace_id = document_tables.workspace_id and wm.user_id = auth.uid() ) ); drop policy if exists "Delete tables within workspace" on public.document_tables; create policy "Delete tables within workspace" on public.document_tables for delete using ( exists ( select 1 from public.workspace_members wm where wm.workspace_id = document_tables.workspace_id and wm.user_id = auth.uid() ) ); create table if not exists public.document_table_rows ( id uuid primary key default gen_random_uuid(), workspace_id uuid not null references public.workspaces(id) on delete cascade, document_id uuid not null references public.documents(id) on delete cascade, table_id uuid not null references public.document_tables(id) on delete cascade, row_index integer not null, row_data jsonb not null default '{}'::jsonb, row_hash text, is_deleted boolean not null default false, updated_by uuid references public.profiles(id) on delete set null, created_at timestamptz not null default timezone('utc', now()), updated_at timestamptz not null default timezone('utc', now()) ); create unique index if not exists document_table_rows_unique_row on public.document_table_rows (table_id, row_index) where is_deleted = false; create index if not exists document_table_rows_workspace_idx on public.document_table_rows (workspace_id, updated_at desc); create index if not exists document_table_rows_table_idx on public.document_table_rows (table_id, row_index); create or replace function public.set_document_table_rows_updated_at() returns trigger language plpgsql security invoker as $$ begin new.updated_at := timezone('utc', now()); return new; end; $$; drop trigger if exists set_document_table_rows_updated_at on public.document_table_rows; create trigger set_document_table_rows_updated_at before update on public.document_table_rows for each row execute procedure public.set_document_table_rows_updated_at(); create or replace function public.sync_document_table_row_meta() returns trigger language plpgsql security invoker as $$ declare v_table record; begin select workspace_id, document_id into v_table from public.document_tables where id = new.table_id; if not found then raise exception 'Document table % not found when creating row', new.table_id; end if; new.workspace_id := v_table.workspace_id; new.document_id := v_table.document_id; return new; end; $$; drop trigger if exists sync_document_table_row_meta on public.document_table_rows; create trigger sync_document_table_row_meta before insert or update on public.document_table_rows for each row execute procedure public.sync_document_table_row_meta(); alter table public.document_table_rows enable row level security; drop policy if exists "Select table rows within workspace" on public.document_table_rows; create policy "Select table rows within workspace" on public.document_table_rows for select using ( exists ( select 1 from public.workspace_members wm where wm.workspace_id = document_table_rows.workspace_id and wm.user_id = auth.uid() ) ); drop policy if exists "Insert table rows within workspace" on public.document_table_rows; create policy "Insert table rows within workspace" on public.document_table_rows for insert with check ( exists ( select 1 from public.workspace_members wm where wm.workspace_id = document_table_rows.workspace_id and wm.user_id = auth.uid() ) ); drop policy if exists "Update table rows within workspace" on public.document_table_rows; create policy "Update table rows within workspace" on public.document_table_rows for update using ( exists ( select 1 from public.workspace_members wm where wm.workspace_id = document_table_rows.workspace_id and wm.user_id = auth.uid() ) ) with check ( exists ( select 1 from public.workspace_members wm where wm.workspace_id = document_table_rows.workspace_id and wm.user_id = auth.uid() ) ); drop policy if exists "Delete table rows within workspace" on public.document_table_rows; create policy "Delete table rows within workspace" on public.document_table_rows for delete using ( exists ( select 1 from public.workspace_members wm where wm.workspace_id = document_table_rows.workspace_id and wm.user_id = auth.uid() ) );