\set ON_ERROR_STOP on BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY; SET LOCAL TIME ZONE 'UTC'; -- 只读导出本地内容,防止误把另一环境的数据当作本次内容来源。 DO $$ BEGIN IF current_database() <> 'doodle_dev' THEN RAISE EXCEPTION '导出只允许读取 doodle_dev'; END IF; IF EXISTS ( SELECT 1 FROM public._prisma_migrations WHERE finished_at IS NULL AND rolled_back_at IS NULL ) THEN RAISE EXCEPTION '源库存在未完成迁移,请先核查'; END IF; END $$; WITH asset_ids AS ( SELECT preview_asset_id AS id FROM public.worksheets UNION SELECT icon_asset_id FROM public.categories WHERE icon_asset_id IS NOT NULL ) SELECT jsonb_build_object( 'format', 'doodle-content-v1', 'source_database', current_database(), 'exported_at', transaction_timestamp(), 'migration_history', ( SELECT COALESCE(jsonb_agg(jsonb_build_object( 'name', migration_name, 'checksum', checksum ) ORDER BY migration_name), '[]'::jsonb) FROM public._prisma_migrations WHERE finished_at IS NOT NULL AND rolled_back_at IS NULL ), -- 按字段名比较签名,兼容经旧 SQL 初始化、字段物理顺序不同的本地库。 'column_signature', ( SELECT jsonb_agg(jsonb_build_object( 'table', c.relname, 'column', a.attname, 'type', format_type(a.atttypid, a.atttypmod), 'not_null', a.attnotnull ) ORDER BY c.relname, a.attname) FROM pg_attribute a JOIN pg_class c ON c.oid = a.attrelid JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = 'public' AND c.relname IN ('assets', 'categories', 'worksheets') AND a.attnum > 0 AND NOT a.attisdropped ), 'excluded_relations', jsonb_build_object( 'worksheet_grades', (SELECT count(*) FROM public.worksheet_grades), 'worksheet_resources', (SELECT count(*) FROM public.worksheet_resources), 'worksheet_resource_assets', (SELECT count(*) FROM public.worksheet_resource_assets) ), 'assets', ( SELECT COALESCE(jsonb_agg(to_jsonb(a) ORDER BY a.id), '[]'::jsonb) FROM public.assets a JOIN asset_ids r ON r.id = a.id ), 'categories', ( SELECT COALESCE(jsonb_agg(to_jsonb(c) ORDER BY c.id), '[]'::jsonb) FROM public.categories c ), 'worksheets', ( SELECT COALESCE(jsonb_agg(to_jsonb(w) ORDER BY w.id), '[]'::jsonb) FROM public.worksheets w ) ); COMMIT;