mirror of
https://github.com/supabase/supabase.git
synced 2026-05-08 07:50:20 +08:00
Exporting all rows (in CSV, SQL, or JSON format) currently uses offset pagination, which can cause performance problems if the table is large. There is also a correctness problem if the table is being actively updated as the export happens, because the relative row offsets could shift between queries. Now that composite filters are available in postgres-meta, we can change to using cursor pagination on the primary key (or any non-null unique keys) wherever possible. Where this is not possible, the user will be shown a confirmation dialog explaining the possible performance impact. --------- Co-authored-by: Ali Waseem <waseema393@gmail.com> Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
312 lines
13 KiB
TypeScript
312 lines
13 KiB
TypeScript
import minify from 'pg-minify'
|
|
|
|
export function getTableEditorSql(id?: number) {
|
|
if (!id) return ''
|
|
|
|
return minify(/* SQL */ `
|
|
with base_table_info as (
|
|
select
|
|
c.oid::int8 as id,
|
|
nc.nspname as schema,
|
|
c.relname as name,
|
|
c.relkind,
|
|
c.relrowsecurity as rls_enabled,
|
|
c.relforcerowsecurity as rls_forced,
|
|
c.relreplident,
|
|
c.relowner,
|
|
obj_description(c.oid) as comment,
|
|
fs.srvname as foreign_server_name,
|
|
fdw.fdwname as foreign_data_wrapper_name,
|
|
fdw_handler.proname as foreign_data_wrapper_handler
|
|
from pg_class c
|
|
join pg_namespace nc on nc.oid = c.relnamespace
|
|
left join pg_foreign_table ft on ft.ftrelid = c.oid
|
|
left join pg_foreign_server fs on fs.oid = ft.ftserver
|
|
left join pg_foreign_data_wrapper fdw on fdw.oid = fs.srvfdw
|
|
left join pg_proc fdw_handler on fdw.fdwhandler = fdw_handler.oid
|
|
where c.oid = ${id}
|
|
and not pg_is_other_temp_schema(nc.oid)
|
|
and (
|
|
pg_has_role(c.relowner, 'USAGE')
|
|
or has_table_privilege(
|
|
c.oid,
|
|
'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER'
|
|
)
|
|
or has_any_column_privilege(c.oid, 'SELECT, INSERT, UPDATE, REFERENCES')
|
|
)
|
|
),
|
|
table_stats as (
|
|
select
|
|
b.id,
|
|
case
|
|
when b.relreplident = 'd' then 'DEFAULT'
|
|
when b.relreplident = 'i' then 'INDEX'
|
|
when b.relreplident = 'f' then 'FULL'
|
|
else 'NOTHING'
|
|
end as replica_identity,
|
|
pg_total_relation_size(format('%I.%I', b.schema, b.name))::int8 as bytes,
|
|
pg_size_pretty(pg_total_relation_size(format('%I.%I', b.schema, b.name))) as size,
|
|
pg_stat_get_live_tuples(b.id) as live_rows_estimate,
|
|
pg_stat_get_dead_tuples(b.id) as dead_rows_estimate
|
|
from base_table_info b
|
|
where b.relkind in ('r', 'p')
|
|
),
|
|
primary_keys as (
|
|
select
|
|
i.indrelid as table_id,
|
|
jsonb_agg(
|
|
jsonb_build_object(
|
|
'schema', n.nspname,
|
|
'table_name', c.relname,
|
|
'table_id', i.indrelid::int8,
|
|
'name', a.attname
|
|
)
|
|
order by array_position(i.indkey, a.attnum)
|
|
) as primary_keys
|
|
from pg_index i
|
|
join pg_class c on i.indrelid = c.oid
|
|
join pg_namespace n on c.relnamespace = n.oid
|
|
join pg_attribute a on a.attrelid = c.oid and a.attnum = any(i.indkey)
|
|
where i.indisprimary
|
|
group by i.indrelid
|
|
),
|
|
index_cols as (
|
|
select
|
|
i.indrelid as table_id,
|
|
i.indkey,
|
|
array_agg(
|
|
a.attname
|
|
order by array_position(i.indkey, a.attnum)
|
|
) as columns
|
|
from pg_index i
|
|
join pg_class c on i.indrelid = c.oid
|
|
join pg_attribute a on a.attrelid = c.oid
|
|
and a.attnum = any(i.indkey)
|
|
where i.indisunique
|
|
and i.indisprimary = false
|
|
group by i.indrelid, i.indkey
|
|
),
|
|
unique_indexes as (
|
|
select
|
|
ic.table_id,
|
|
jsonb_agg(
|
|
jsonb_build_object(
|
|
'schema', n.nspname,
|
|
'table_name', c.relname,
|
|
'table_id', ic.table_id::int8,
|
|
'columns', ic.columns
|
|
)
|
|
) as unique_indexes
|
|
from index_cols ic
|
|
join pg_class c on c.oid = ic.table_id
|
|
join pg_namespace n on n.oid = c.relnamespace
|
|
group by ic.table_id
|
|
),
|
|
relationships as (
|
|
select
|
|
c.conrelid as source_id,
|
|
c.confrelid as target_id,
|
|
jsonb_build_object(
|
|
'id', c.oid::int8,
|
|
'constraint_name', c.conname,
|
|
'deletion_action', c.confdeltype,
|
|
'update_action', c.confupdtype,
|
|
'source_schema', nsa.nspname,
|
|
'source_table_name', csa.relname,
|
|
'source_column_name', sa.attname,
|
|
'target_table_schema', nta.nspname,
|
|
'target_table_name', cta.relname,
|
|
'target_column_name', ta.attname
|
|
) as rel_info
|
|
from pg_constraint c
|
|
join pg_class csa on c.conrelid = csa.oid
|
|
join pg_namespace nsa on csa.relnamespace = nsa.oid
|
|
join pg_attribute sa on (sa.attrelid = c.conrelid and sa.attnum = any(c.conkey))
|
|
join pg_class cta on c.confrelid = cta.oid
|
|
join pg_namespace nta on cta.relnamespace = nta.oid
|
|
join pg_attribute ta on (ta.attrelid = c.confrelid and ta.attnum = any(c.confkey))
|
|
where c.contype = 'f'
|
|
),
|
|
columns as (
|
|
select
|
|
a.attrelid as table_id,
|
|
jsonb_agg(jsonb_build_object(
|
|
'id', (a.attrelid || '.' || a.attnum),
|
|
'table_id', c.oid::int8,
|
|
'schema', nc.nspname,
|
|
'table', c.relname,
|
|
'ordinal_position', a.attnum,
|
|
'name', a.attname,
|
|
'default_value', case
|
|
when a.atthasdef then pg_get_expr(ad.adbin, ad.adrelid)
|
|
else null
|
|
end,
|
|
'data_type', case
|
|
when t.typtype = 'd' then
|
|
case
|
|
when bt.typelem <> 0::oid and bt.typlen = -1 then 'ARRAY'
|
|
when nbt.nspname = 'pg_catalog' then format_type(t.typbasetype, null)
|
|
else 'USER-DEFINED'
|
|
end
|
|
else
|
|
case
|
|
when t.typelem <> 0::oid and t.typlen = -1 then 'ARRAY'
|
|
when nt.nspname = 'pg_catalog' then format_type(a.atttypid, null)
|
|
else 'USER-DEFINED'
|
|
end
|
|
end,
|
|
'format', case
|
|
when t.typtype = 'e' then
|
|
case
|
|
when nt.nspname <> 'public' then concat(nt.nspname, '.', coalesce(bt.typname, t.typname))
|
|
else coalesce(bt.typname, t.typname)
|
|
end
|
|
else
|
|
coalesce(bt.typname, t.typname)
|
|
end,
|
|
'is_identity', a.attidentity in ('a', 'd'),
|
|
'identity_generation', case a.attidentity
|
|
when 'a' then 'ALWAYS'
|
|
when 'd' then 'BY DEFAULT'
|
|
else null
|
|
end,
|
|
'is_generated', a.attgenerated in ('s'),
|
|
'is_nullable', not (a.attnotnull or t.typtype = 'd' and t.typnotnull),
|
|
'is_updatable', (
|
|
b.relkind in ('r', 'p') or
|
|
(b.relkind in ('v', 'f') and pg_column_is_updatable(b.id, a.attnum, false))
|
|
),
|
|
'is_unique', uniques.table_id is not null,
|
|
'check', check_constraints.definition,
|
|
'comment', col_description(c.oid, a.attnum),
|
|
'enums', coalesce(
|
|
(
|
|
select jsonb_agg(e.enumlabel order by e.enumsortorder)
|
|
from pg_catalog.pg_enum e
|
|
where e.enumtypid = coalesce(bt.oid, t.oid)
|
|
or e.enumtypid = coalesce(bt.typelem, t.typelem)
|
|
),
|
|
'[]'::jsonb
|
|
)
|
|
) order by a.attnum) as columns
|
|
from pg_attribute a
|
|
join base_table_info b on a.attrelid = b.id
|
|
join pg_class c on a.attrelid = c.oid
|
|
join pg_namespace nc on c.relnamespace = nc.oid
|
|
left join pg_attrdef ad on (a.attrelid = ad.adrelid and a.attnum = ad.adnum)
|
|
join pg_type t on a.atttypid = t.oid
|
|
join pg_namespace nt on t.typnamespace = nt.oid
|
|
left join pg_type bt on (t.typtype = 'd' and t.typbasetype = bt.oid)
|
|
left join pg_namespace nbt on bt.typnamespace = nbt.oid
|
|
left join (
|
|
select
|
|
conrelid as table_id,
|
|
conkey[1] as ordinal_position
|
|
from pg_catalog.pg_constraint
|
|
where contype = 'u' and cardinality(conkey) = 1
|
|
group by conrelid, conkey[1]
|
|
) as uniques on uniques.table_id = a.attrelid and uniques.ordinal_position = a.attnum
|
|
left join (
|
|
select distinct on (conrelid, conkey[1])
|
|
conrelid as table_id,
|
|
conkey[1] as ordinal_position,
|
|
substring(
|
|
pg_get_constraintdef(oid, true),
|
|
8,
|
|
length(pg_get_constraintdef(oid, true)) - 8
|
|
) as definition
|
|
from pg_constraint
|
|
where contype = 'c' and cardinality(conkey) = 1
|
|
order by conrelid, conkey[1], oid asc
|
|
) as check_constraints on check_constraints.table_id = a.attrelid
|
|
and check_constraints.ordinal_position = a.attnum
|
|
where a.attnum > 0
|
|
and not a.attisdropped
|
|
group by a.attrelid
|
|
)
|
|
select
|
|
case b.relkind
|
|
when 'r' then jsonb_build_object(
|
|
'entity_type', b.relkind,
|
|
'id', b.id,
|
|
'schema', b.schema,
|
|
'name', b.name,
|
|
'rls_enabled', b.rls_enabled,
|
|
'rls_forced', b.rls_forced,
|
|
'replica_identity', ts.replica_identity,
|
|
'bytes', ts.bytes,
|
|
'size', ts.size,
|
|
'live_rows_estimate', ts.live_rows_estimate,
|
|
'dead_rows_estimate', ts.dead_rows_estimate,
|
|
'comment', b.comment,
|
|
'primary_keys', coalesce(pk.primary_keys, '[]'::jsonb),
|
|
'unique_indexes', coalesce(ui.unique_indexes, '[]'::jsonb),
|
|
'relationships', coalesce(
|
|
(select jsonb_agg(r.rel_info)
|
|
from relationships r
|
|
where r.source_id = b.id or r.target_id = b.id),
|
|
'[]'::jsonb
|
|
),
|
|
'columns', coalesce(c.columns, '[]'::jsonb)
|
|
)
|
|
when 'p' then jsonb_build_object(
|
|
'entity_type', b.relkind,
|
|
'id', b.id,
|
|
'schema', b.schema,
|
|
'name', b.name,
|
|
'rls_enabled', b.rls_enabled,
|
|
'rls_forced', b.rls_forced,
|
|
'replica_identity', ts.replica_identity,
|
|
'bytes', ts.bytes,
|
|
'size', ts.size,
|
|
'live_rows_estimate', ts.live_rows_estimate,
|
|
'dead_rows_estimate', ts.dead_rows_estimate,
|
|
'comment', b.comment,
|
|
'primary_keys', coalesce(pk.primary_keys, '[]'::jsonb),
|
|
'unique_indexes', coalesce(ui.unique_indexes, '[]'::jsonb),
|
|
'relationships', coalesce(
|
|
(select jsonb_agg(r.rel_info)
|
|
from relationships r
|
|
where r.source_id = b.id or r.target_id = b.id),
|
|
'[]'::jsonb
|
|
),
|
|
'columns', coalesce(c.columns, '[]'::jsonb)
|
|
)
|
|
when 'v' then jsonb_build_object(
|
|
'entity_type', b.relkind,
|
|
'id', b.id,
|
|
'schema', b.schema,
|
|
'name', b.name,
|
|
'is_updatable', (pg_relation_is_updatable(b.id, false) & 20) = 20,
|
|
'comment', b.comment,
|
|
'columns', coalesce(c.columns, '[]'::jsonb)
|
|
)
|
|
when 'm' then jsonb_build_object(
|
|
'entity_type', b.relkind,
|
|
'id', b.id,
|
|
'schema', b.schema,
|
|
'name', b.name,
|
|
'is_populated', true,
|
|
'comment', b.comment,
|
|
'columns', coalesce(c.columns, '[]'::jsonb)
|
|
)
|
|
when 'f' then jsonb_build_object(
|
|
'entity_type', b.relkind,
|
|
'id', b.id,
|
|
'schema', b.schema,
|
|
'name', b.name,
|
|
'comment', b.comment,
|
|
'foreign_server_name', b.foreign_server_name,
|
|
'foreign_data_wrapper_name', b.foreign_data_wrapper_name,
|
|
'foreign_data_wrapper_handler', b.foreign_data_wrapper_handler,
|
|
'columns', coalesce(c.columns, '[]'::jsonb)
|
|
)
|
|
end as entity
|
|
from base_table_info b
|
|
left join table_stats ts on b.id = ts.id
|
|
left join primary_keys pk on b.id = pk.table_id
|
|
left join unique_indexes ui on b.id = ui.table_id
|
|
left join columns c on b.id = c.table_id;
|
|
`)
|
|
}
|