table.ts335 lines · main
| 1 | import { ident, joinSqlFragments, literal, safeSql, type SafeSqlFragment } from '../../../pg-format' |
| 2 | |
| 3 | export const getDuplicateTableSQL = ({ |
| 4 | comment, |
| 5 | duplicatedTableName, |
| 6 | sourceTableName, |
| 7 | sourceTableSchema, |
| 8 | }: { |
| 9 | comment?: string | null |
| 10 | duplicatedTableName: string |
| 11 | sourceTableName: string |
| 12 | sourceTableSchema: string |
| 13 | }): SafeSqlFragment => { |
| 14 | const createSql = safeSql`CREATE TABLE ${ident(sourceTableSchema)}.${ident(duplicatedTableName)} (LIKE ${ident(sourceTableSchema)}.${ident(sourceTableName)} INCLUDING ALL);` |
| 15 | const commentSql = |
| 16 | comment != undefined |
| 17 | ? safeSql`comment on table ${ident(sourceTableSchema)}.${ident(duplicatedTableName)} is ${literal(comment)};` |
| 18 | : safeSql`` |
| 19 | return joinSqlFragments([createSql, commentSql], '\n') |
| 20 | } |
| 21 | |
| 22 | export const getDuplicateRowsSQL = ({ |
| 23 | duplicatedTableName, |
| 24 | sourceTableName, |
| 25 | sourceTableSchema, |
| 26 | }: { |
| 27 | duplicatedTableName: string |
| 28 | sourceTableName: string |
| 29 | sourceTableSchema: string |
| 30 | }): SafeSqlFragment => { |
| 31 | return safeSql`INSERT INTO ${ident(sourceTableSchema)}.${ident(duplicatedTableName)} SELECT * FROM ${ident(sourceTableSchema)}.${ident(sourceTableName)};` |
| 32 | } |
| 33 | |
| 34 | export const getTableEditorSql = ({ id }: { id?: number }): SafeSqlFragment => { |
| 35 | if (!id) return safeSql`` |
| 36 | |
| 37 | return safeSql` |
| 38 | with base_table_info as ( |
| 39 | select |
| 40 | c.oid::int8 as id, |
| 41 | nc.nspname as schema, |
| 42 | c.relname as name, |
| 43 | c.relkind, |
| 44 | c.relrowsecurity as rls_enabled, |
| 45 | c.relforcerowsecurity as rls_forced, |
| 46 | c.relreplident, |
| 47 | c.relowner, |
| 48 | obj_description(c.oid) as comment, |
| 49 | fs.srvname as foreign_server_name, |
| 50 | fdw.fdwname as foreign_data_wrapper_name, |
| 51 | fdw_handler.proname as foreign_data_wrapper_handler |
| 52 | from pg_class c |
| 53 | join pg_namespace nc on nc.oid = c.relnamespace |
| 54 | left join pg_foreign_table ft on ft.ftrelid = c.oid |
| 55 | left join pg_foreign_server fs on fs.oid = ft.ftserver |
| 56 | left join pg_foreign_data_wrapper fdw on fdw.oid = fs.srvfdw |
| 57 | left join pg_proc fdw_handler on fdw.fdwhandler = fdw_handler.oid |
| 58 | where c.oid = ${literal(id)} |
| 59 | and not pg_is_other_temp_schema(nc.oid) |
| 60 | and ( |
| 61 | pg_has_role(c.relowner, 'USAGE') |
| 62 | or has_table_privilege( |
| 63 | c.oid, |
| 64 | 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER' |
| 65 | ) |
| 66 | or has_any_column_privilege(c.oid, 'SELECT, INSERT, UPDATE, REFERENCES') |
| 67 | ) |
| 68 | ), |
| 69 | table_stats as ( |
| 70 | select |
| 71 | b.id, |
| 72 | case |
| 73 | when b.relreplident = 'd' then 'DEFAULT' |
| 74 | when b.relreplident = 'i' then 'INDEX' |
| 75 | when b.relreplident = 'f' then 'FULL' |
| 76 | else 'NOTHING' |
| 77 | end as replica_identity, |
| 78 | pg_total_relation_size(format('%I.%I', b.schema, b.name))::int8 as bytes, |
| 79 | pg_size_pretty(pg_total_relation_size(format('%I.%I', b.schema, b.name))) as size, |
| 80 | pg_stat_get_live_tuples(b.id) as live_rows_estimate, |
| 81 | pg_stat_get_dead_tuples(b.id) as dead_rows_estimate |
| 82 | from base_table_info b |
| 83 | where b.relkind in ('r', 'p') |
| 84 | ), |
| 85 | primary_keys as ( |
| 86 | select |
| 87 | i.indrelid as table_id, |
| 88 | jsonb_agg( |
| 89 | jsonb_build_object( |
| 90 | 'schema', n.nspname, |
| 91 | 'table_name', c.relname, |
| 92 | 'table_id', i.indrelid::int8, |
| 93 | 'name', a.attname |
| 94 | ) |
| 95 | order by array_position(i.indkey, a.attnum) |
| 96 | ) as primary_keys |
| 97 | from pg_index i |
| 98 | join pg_class c on i.indrelid = c.oid |
| 99 | join pg_namespace n on c.relnamespace = n.oid |
| 100 | join pg_attribute a on a.attrelid = c.oid and a.attnum = any(i.indkey) |
| 101 | where i.indisprimary |
| 102 | group by i.indrelid |
| 103 | ), |
| 104 | index_cols as ( |
| 105 | select |
| 106 | i.indrelid as table_id, |
| 107 | i.indkey, |
| 108 | array_agg( |
| 109 | a.attname |
| 110 | order by array_position(i.indkey, a.attnum) |
| 111 | ) as columns |
| 112 | from pg_index i |
| 113 | join pg_class c on i.indrelid = c.oid |
| 114 | join pg_attribute a on a.attrelid = c.oid |
| 115 | and a.attnum = any(i.indkey) |
| 116 | where i.indisunique |
| 117 | and i.indisprimary = false |
| 118 | group by i.indrelid, i.indkey |
| 119 | ), |
| 120 | unique_indexes as ( |
| 121 | select |
| 122 | ic.table_id, |
| 123 | jsonb_agg( |
| 124 | jsonb_build_object( |
| 125 | 'schema', n.nspname, |
| 126 | 'table_name', c.relname, |
| 127 | 'table_id', ic.table_id::int8, |
| 128 | 'columns', ic.columns |
| 129 | ) |
| 130 | ) as unique_indexes |
| 131 | from index_cols ic |
| 132 | join pg_class c on c.oid = ic.table_id |
| 133 | join pg_namespace n on n.oid = c.relnamespace |
| 134 | group by ic.table_id |
| 135 | ), |
| 136 | relationships as ( |
| 137 | select |
| 138 | c.conrelid as source_id, |
| 139 | c.confrelid as target_id, |
| 140 | jsonb_build_object( |
| 141 | 'id', c.oid::int8, |
| 142 | 'constraint_name', c.conname, |
| 143 | 'deletion_action', c.confdeltype, |
| 144 | 'update_action', c.confupdtype, |
| 145 | 'source_schema', nsa.nspname, |
| 146 | 'source_table_name', csa.relname, |
| 147 | 'source_column_name', sa.attname, |
| 148 | 'target_table_schema', nta.nspname, |
| 149 | 'target_table_name', cta.relname, |
| 150 | 'target_column_name', ta.attname |
| 151 | ) as rel_info |
| 152 | from pg_constraint c |
| 153 | join pg_class csa on c.conrelid = csa.oid |
| 154 | join pg_namespace nsa on csa.relnamespace = nsa.oid |
| 155 | join pg_attribute sa on (sa.attrelid = c.conrelid and sa.attnum = any(c.conkey)) |
| 156 | join pg_class cta on c.confrelid = cta.oid |
| 157 | join pg_namespace nta on cta.relnamespace = nta.oid |
| 158 | join pg_attribute ta on (ta.attrelid = c.confrelid and ta.attnum = any(c.confkey)) |
| 159 | where c.contype = 'f' |
| 160 | ), |
| 161 | columns as ( |
| 162 | select |
| 163 | a.attrelid as table_id, |
| 164 | jsonb_agg(jsonb_build_object( |
| 165 | 'id', (a.attrelid || '.' || a.attnum), |
| 166 | 'table_id', c.oid::int8, |
| 167 | 'schema', nc.nspname, |
| 168 | 'table', c.relname, |
| 169 | 'ordinal_position', a.attnum, |
| 170 | 'name', a.attname, |
| 171 | 'default_value', case |
| 172 | when a.atthasdef then pg_get_expr(ad.adbin, ad.adrelid) |
| 173 | else null |
| 174 | end, |
| 175 | 'data_type', case |
| 176 | when t.typtype = 'd' then |
| 177 | case |
| 178 | when bt.typelem <> 0::oid and bt.typlen = -1 then 'ARRAY' |
| 179 | when nbt.nspname = 'pg_catalog' then format_type(t.typbasetype, null) |
| 180 | else 'USER-DEFINED' |
| 181 | end |
| 182 | else |
| 183 | case |
| 184 | when t.typelem <> 0::oid and t.typlen = -1 then 'ARRAY' |
| 185 | when nt.nspname = 'pg_catalog' then format_type(a.atttypid, null) |
| 186 | else 'USER-DEFINED' |
| 187 | end |
| 188 | end, |
| 189 | 'format', coalesce(bt.typname, t.typname), |
| 190 | 'format_schema', coalesce(nbt.nspname, nt.nspname), |
| 191 | 'is_identity', a.attidentity in ('a', 'd'), |
| 192 | 'identity_generation', case a.attidentity |
| 193 | when 'a' then 'ALWAYS' |
| 194 | when 'd' then 'BY DEFAULT' |
| 195 | else null |
| 196 | end, |
| 197 | 'is_generated', a.attgenerated in ('s'), |
| 198 | 'is_nullable', not (a.attnotnull or t.typtype = 'd' and t.typnotnull), |
| 199 | 'is_updatable', ( |
| 200 | b.relkind in ('r', 'p') or |
| 201 | (b.relkind in ('v', 'f') and pg_column_is_updatable(b.id, a.attnum, false)) |
| 202 | ), |
| 203 | 'is_unique', uniques.table_id is not null, |
| 204 | 'check', check_constraints.definition, |
| 205 | 'comment', col_description(c.oid, a.attnum), |
| 206 | 'enums', coalesce( |
| 207 | ( |
| 208 | select jsonb_agg(e.enumlabel order by e.enumsortorder) |
| 209 | from pg_catalog.pg_enum e |
| 210 | where e.enumtypid = coalesce(bt.oid, t.oid) |
| 211 | or e.enumtypid = coalesce(bt.typelem, t.typelem) |
| 212 | ), |
| 213 | '[]'::jsonb |
| 214 | ) |
| 215 | ) order by a.attnum) as columns |
| 216 | from pg_attribute a |
| 217 | join base_table_info b on a.attrelid = b.id |
| 218 | join pg_class c on a.attrelid = c.oid |
| 219 | join pg_namespace nc on c.relnamespace = nc.oid |
| 220 | left join pg_attrdef ad on (a.attrelid = ad.adrelid and a.attnum = ad.adnum) |
| 221 | join pg_type t on a.atttypid = t.oid |
| 222 | join pg_namespace nt on t.typnamespace = nt.oid |
| 223 | left join pg_type bt on (t.typtype = 'd' and t.typbasetype = bt.oid) |
| 224 | left join pg_namespace nbt on bt.typnamespace = nbt.oid |
| 225 | left join ( |
| 226 | select |
| 227 | conrelid as table_id, |
| 228 | conkey[1] as ordinal_position |
| 229 | from pg_catalog.pg_constraint |
| 230 | where contype = 'u' and cardinality(conkey) = 1 |
| 231 | group by conrelid, conkey[1] |
| 232 | ) as uniques on uniques.table_id = a.attrelid and uniques.ordinal_position = a.attnum |
| 233 | left join ( |
| 234 | select distinct on (conrelid, conkey[1]) |
| 235 | conrelid as table_id, |
| 236 | conkey[1] as ordinal_position, |
| 237 | substring( |
| 238 | pg_get_constraintdef(oid, true), |
| 239 | 8, |
| 240 | length(pg_get_constraintdef(oid, true)) - 8 |
| 241 | ) as definition |
| 242 | from pg_constraint |
| 243 | where contype = 'c' and cardinality(conkey) = 1 |
| 244 | order by conrelid, conkey[1], oid asc |
| 245 | ) as check_constraints on check_constraints.table_id = a.attrelid |
| 246 | and check_constraints.ordinal_position = a.attnum |
| 247 | where a.attnum > 0 |
| 248 | and not a.attisdropped |
| 249 | group by a.attrelid |
| 250 | ) |
| 251 | select |
| 252 | case b.relkind |
| 253 | when 'r' then jsonb_build_object( |
| 254 | 'entity_type', b.relkind, |
| 255 | 'id', b.id, |
| 256 | 'schema', b.schema, |
| 257 | 'name', b.name, |
| 258 | 'rls_enabled', b.rls_enabled, |
| 259 | 'rls_forced', b.rls_forced, |
| 260 | 'replica_identity', ts.replica_identity, |
| 261 | 'bytes', ts.bytes, |
| 262 | 'size', ts.size, |
| 263 | 'live_rows_estimate', ts.live_rows_estimate, |
| 264 | 'dead_rows_estimate', ts.dead_rows_estimate, |
| 265 | 'comment', b.comment, |
| 266 | 'primary_keys', coalesce(pk.primary_keys, '[]'::jsonb), |
| 267 | 'unique_indexes', coalesce(ui.unique_indexes, '[]'::jsonb), |
| 268 | 'relationships', coalesce( |
| 269 | (select jsonb_agg(r.rel_info) |
| 270 | from relationships r |
| 271 | where r.source_id = b.id or r.target_id = b.id), |
| 272 | '[]'::jsonb |
| 273 | ), |
| 274 | 'columns', coalesce(c.columns, '[]'::jsonb) |
| 275 | ) |
| 276 | when 'p' then jsonb_build_object( |
| 277 | 'entity_type', b.relkind, |
| 278 | 'id', b.id, |
| 279 | 'schema', b.schema, |
| 280 | 'name', b.name, |
| 281 | 'rls_enabled', b.rls_enabled, |
| 282 | 'rls_forced', b.rls_forced, |
| 283 | 'replica_identity', ts.replica_identity, |
| 284 | 'bytes', ts.bytes, |
| 285 | 'size', ts.size, |
| 286 | 'live_rows_estimate', ts.live_rows_estimate, |
| 287 | 'dead_rows_estimate', ts.dead_rows_estimate, |
| 288 | 'comment', b.comment, |
| 289 | 'primary_keys', coalesce(pk.primary_keys, '[]'::jsonb), |
| 290 | 'unique_indexes', coalesce(ui.unique_indexes, '[]'::jsonb), |
| 291 | 'relationships', coalesce( |
| 292 | (select jsonb_agg(r.rel_info) |
| 293 | from relationships r |
| 294 | where r.source_id = b.id or r.target_id = b.id), |
| 295 | '[]'::jsonb |
| 296 | ), |
| 297 | 'columns', coalesce(c.columns, '[]'::jsonb) |
| 298 | ) |
| 299 | when 'v' then jsonb_build_object( |
| 300 | 'entity_type', b.relkind, |
| 301 | 'id', b.id, |
| 302 | 'schema', b.schema, |
| 303 | 'name', b.name, |
| 304 | 'is_updatable', (pg_relation_is_updatable(b.id, false) & 20) = 20, |
| 305 | 'comment', b.comment, |
| 306 | 'columns', coalesce(c.columns, '[]'::jsonb) |
| 307 | ) |
| 308 | when 'm' then jsonb_build_object( |
| 309 | 'entity_type', b.relkind, |
| 310 | 'id', b.id, |
| 311 | 'schema', b.schema, |
| 312 | 'name', b.name, |
| 313 | 'is_populated', true, |
| 314 | 'comment', b.comment, |
| 315 | 'columns', coalesce(c.columns, '[]'::jsonb) |
| 316 | ) |
| 317 | when 'f' then jsonb_build_object( |
| 318 | 'entity_type', b.relkind, |
| 319 | 'id', b.id, |
| 320 | 'schema', b.schema, |
| 321 | 'name', b.name, |
| 322 | 'comment', b.comment, |
| 323 | 'foreign_server_name', b.foreign_server_name, |
| 324 | 'foreign_data_wrapper_name', b.foreign_data_wrapper_name, |
| 325 | 'foreign_data_wrapper_handler', b.foreign_data_wrapper_handler, |
| 326 | 'columns', coalesce(c.columns, '[]'::jsonb) |
| 327 | ) |
| 328 | end as entity |
| 329 | from base_table_info b |
| 330 | left join table_stats ts on b.id = ts.id |
| 331 | left join primary_keys pk on b.id = pk.table_id |
| 332 | left join unique_indexes ui on b.id = ui.table_id |
| 333 | left join columns c on b.id = c.table_id; |
| 334 | ` |
| 335 | } |