mirror of
https://github.com/supabase/supabase.git
synced 2026-06-21 22:12:50 +08:00
There is an edge case interaction between the Postgres query parser and MSSQL foreign tables, where the query parser may drop sort clauses that are redundant with applied filters. This leads to invalid MSSQL syntax, because the resulting query has a `limit` but no `sort`, and the user sees a confusing error message. This PR detects this edge case on MSSQL foreign tables. There are three cases: 1. The user filters by a column, but there are still other columns available for sorting. The default search for a sorting column will leave out the filtered column. 2. The user filters by a column/several columns, and there are no more columns that can be used for sorting. We stop the query and show an admonition. 3. The user filters by a column, then tries to sort by the same column. We stop the query and show an admonition.
274 lines
11 KiB
TypeScript
274 lines
11 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
|
|
)) as primary_keys
|
|
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))
|
|
join pg_namespace n on c.relnamespace = n.oid
|
|
where i.indisprimary
|
|
group by i.indrelid
|
|
),
|
|
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),
|
|
'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),
|
|
'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 columns c on b.id = c.table_id;
|
|
`)
|
|
}
|