From 56a1f74e3dfadc3b52bab0c9ab8e38ee5350e486 Mon Sep 17 00:00:00 2001 From: avallete Date: Thu, 28 Aug 2025 18:20:58 +0200 Subject: [PATCH 1/7] wip: optimize queries --- src/lib/PostgresMetaColumns.ts | 24 ++--- src/lib/PostgresMetaTables.ts | 21 +++- src/lib/generators.ts | 36 +++++++ src/lib/sql/{columns.sql => columns.sql.ts} | 3 + src/lib/sql/foreign_tables.sql.ts | 13 +++ src/lib/sql/functions.sql.ts | 111 ++++++++++++++++++++ src/lib/sql/index.ts | 6 +- src/lib/sql/materialized_views.sql.ts | 14 +++ src/lib/sql/{tables.sql => table.sql.ts} | 6 ++ src/lib/sql/views.sql.ts | 15 +++ src/server/templates/typescript.ts | 9 +- 11 files changed, 237 insertions(+), 21 deletions(-) rename src/lib/sql/{columns.sql => columns.sql.ts} (96%) create mode 100644 src/lib/sql/foreign_tables.sql.ts create mode 100644 src/lib/sql/functions.sql.ts create mode 100644 src/lib/sql/materialized_views.sql.ts rename src/lib/sql/{tables.sql => table.sql.ts} (90%) create mode 100644 src/lib/sql/views.sql.ts diff --git a/src/lib/PostgresMetaColumns.ts b/src/lib/PostgresMetaColumns.ts index 15e56507..f888baff 100644 --- a/src/lib/PostgresMetaColumns.ts +++ b/src/lib/PostgresMetaColumns.ts @@ -1,9 +1,9 @@ import { ident, literal } from 'pg-format' import PostgresMetaTables from './PostgresMetaTables.js' import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' -import { columnsSql } from './sql/index.js' import { PostgresMetaResult, PostgresColumn } from './types.js' import { filterByList } from './helpers.js' +import { COLUMNS_SQL } from './sql/columns.sql.js' export default class PostgresMetaColumns { query: (sql: string) => Promise> @@ -29,23 +29,20 @@ export default class PostgresMetaColumns { limit?: number offset?: number } = {}): Promise> { + const schemaFilter = filterByList( + includedSchemas, + excludedSchemas, + !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined + ) let sql = ` WITH - columns AS (${columnsSql}) + columns AS (${COLUMNS_SQL(schemaFilter)}) SELECT * FROM columns WHERE true` - const filter = filterByList( - includedSchemas, - excludedSchemas, - !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined - ) - if (filter) { - sql += ` AND schema ${filter}` - } if (tableId !== undefined) { sql += ` AND table_id = ${literal(tableId)}` } @@ -79,6 +76,7 @@ WHERE table?: string schema?: string }): Promise> { + const schemaFilter = schema ? filterByList([schema], []) : undefined if (id) { const regexp = /^(\d+)\.(\d+)$/ if (!regexp.test(id)) { @@ -86,7 +84,7 @@ WHERE } const matches = id.match(regexp) as RegExpMatchArray const [tableId, ordinalPos] = matches.slice(1).map(Number) - const sql = `${columnsSql} AND c.oid = ${tableId} AND a.attnum = ${ordinalPos};` + const sql = `${COLUMNS_SQL(schemaFilter)} AND c.oid = ${tableId} AND a.attnum = ${ordinalPos};` const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -96,9 +94,9 @@ WHERE return { data: data[0], error } } } else if (name && table) { - const sql = `${columnsSql} AND a.attname = ${literal(name)} AND c.relname = ${literal( + const sql = `${COLUMNS_SQL(schemaFilter)} AND a.attname = ${literal(name)} AND c.relname = ${literal( table - )} AND nc.nspname = ${literal(schema)};` + )};` const { data, error } = await this.query(sql) if (error) { return { data, error } diff --git a/src/lib/PostgresMetaTables.ts b/src/lib/PostgresMetaTables.ts index 5b97c253..0d7e277a 100644 --- a/src/lib/PostgresMetaTables.ts +++ b/src/lib/PostgresMetaTables.ts @@ -1,13 +1,14 @@ import { ident, literal } from 'pg-format' import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' import { coalesceRowsToArray, filterByList } from './helpers.js' -import { columnsSql, tablesSql } from './sql/index.js' import { PostgresMetaResult, PostgresTable, PostgresTableCreate, PostgresTableUpdate, } from './types.js' +import { TABLES_SQL } from './sql/table.sql.js' +import { COLUMNS_SQL } from './sql/columns.sql.js' export default class PostgresMetaTables { query: (sql: string) => Promise> @@ -47,12 +48,12 @@ export default class PostgresMetaTables { offset?: number includeColumns?: boolean } = {}): Promise> { - let sql = generateEnrichedTablesSql({ includeColumns }) const filter = filterByList( includedSchemas, excludedSchemas, !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined ) + let sql = generateEnrichedTablesSql({ includeColumns, schemaFilter: filter }) if (filter) { sql += ` where schema ${filter}` } @@ -62,6 +63,7 @@ export default class PostgresMetaTables { if (offset) { sql += ` offset ${offset}` } + console.log('sql tables: ', sql) return await this.query(sql) } @@ -82,8 +84,10 @@ export default class PostgresMetaTables { name?: string schema?: string }): Promise> { + const schemaFilter = schema ? filterByList([schema], []) : undefined if (id) { const sql = `${generateEnrichedTablesSql({ + schemaFilter, includeColumns: true, })} where tables.id = ${literal(id)};` const { data, error } = await this.query(sql) @@ -96,6 +100,7 @@ export default class PostgresMetaTables { } } else if (name) { const sql = `${generateEnrichedTablesSql({ + schemaFilter, includeColumns: true, })} where tables.name = ${literal(name)} and tables.schema = ${literal(schema)};` const { data, error } = await this.query(sql) @@ -247,9 +252,15 @@ COMMIT;` } } -const generateEnrichedTablesSql = ({ includeColumns }: { includeColumns: boolean }) => ` -with tables as (${tablesSql}) - ${includeColumns ? `, columns as (${columnsSql})` : ''} +const generateEnrichedTablesSql = ({ + includeColumns, + schemaFilter, +}: { + includeColumns: boolean + schemaFilter?: string +}) => ` +with tables as (${TABLES_SQL(schemaFilter)}) + ${includeColumns ? `, columns as (${COLUMNS_SQL(schemaFilter)})` : ''} select * ${includeColumns ? `, ${coalesceRowsToArray('columns', 'columns.table_id = tables.id')}` : ''} diff --git a/src/lib/generators.ts b/src/lib/generators.ts index c916a44c..2d729636 100644 --- a/src/lib/generators.ts +++ b/src/lib/generators.ts @@ -31,6 +31,8 @@ export async function getGeneratorMetadata( excludedSchemas: [], } ): Promise> { + const start = Date.now() + console.log('getGeneratorMetadata start: ') const includedSchemas = filters.includedSchemas ?? [] const excludedSchemas = filters.excludedSchemas ?? [] @@ -48,6 +50,8 @@ export async function getGeneratorMetadata( return { data: null, error: tablesError } } + const startForeignTables = Date.now() + console.log('getGeneratorMetadata foreignTables start: ', startForeignTables) const { data: foreignTables, error: foreignTablesError } = await pgMeta.foreignTables.list({ includedSchemas: includedSchemas.length > 0 ? includedSchemas : undefined, excludedSchemas, @@ -56,7 +60,12 @@ export async function getGeneratorMetadata( if (foreignTablesError) { return { data: null, error: foreignTablesError } } + const endForeignTables = Date.now() + console.log('getGeneratorMetadata foreignTables end: ', endForeignTables) + console.log('elapsedForeignTables: ', endForeignTables - startForeignTables) + const startViews = Date.now() + console.log('getGeneratorMetadata views start: ', startViews) const { data: views, error: viewsError } = await pgMeta.views.list({ includedSchemas: includedSchemas.length > 0 ? includedSchemas : undefined, excludedSchemas, @@ -75,7 +84,12 @@ export async function getGeneratorMetadata( if (materializedViewsError) { return { data: null, error: materializedViewsError } } + const endViews = Date.now() + console.log('getGeneratorMetadata views end: ', endViews) + console.log('elapsedViews: ', endViews - startViews) + const startColumns = Date.now() + console.log('getGeneratorMetadata columns start: ', startColumns) const { data: columns, error: columnsError } = await pgMeta.columns.list({ includedSchemas: includedSchemas.length > 0 ? includedSchemas : undefined, excludedSchemas, @@ -83,12 +97,22 @@ export async function getGeneratorMetadata( if (columnsError) { return { data: null, error: columnsError } } + const endColumns = Date.now() + console.log('getGeneratorMetadata columns end: ', endColumns) + console.log('elapsedColumns: ', endColumns - startColumns) + const startRelationships = Date.now() + console.log('getGeneratorMetadata relationships start: ', startRelationships) const { data: relationships, error: relationshipsError } = await pgMeta.relationships.list() if (relationshipsError) { return { data: null, error: relationshipsError } } + const endRelationships = Date.now() + console.log('getGeneratorMetadata relationships end: ', endRelationships) + console.log('elapsedRelationships: ', endRelationships - startRelationships) + const startFunctions = Date.now() + console.log('getGeneratorMetadata functions start: ', startFunctions) const { data: functions, error: functionsError } = await pgMeta.functions.list({ includedSchemas: includedSchemas.length > 0 ? includedSchemas : undefined, excludedSchemas, @@ -96,7 +120,12 @@ export async function getGeneratorMetadata( if (functionsError) { return { data: null, error: functionsError } } + const endFunctions = Date.now() + console.log('getGeneratorMetadata functions end: ', endFunctions) + console.log('elapsedFunctions: ', endFunctions - startFunctions) + const startTypes = Date.now() + console.log('getGeneratorMetadata types start: ', startTypes) const { data: types, error: typesError } = await pgMeta.types.list({ includeTableTypes: true, includeArrayTypes: true, @@ -105,9 +134,16 @@ export async function getGeneratorMetadata( if (typesError) { return { data: null, error: typesError } } + const endTypes = Date.now() + console.log('getGeneratorMetadata types end: ', endTypes) + console.log('elapsedTypes: ', endTypes - startTypes) await pgMeta.end() + const end = Date.now() + console.log('getGeneratorMetadata end: ', end) + console.log('elapsed: ', end - start) + return { data: { schemas: schemas.filter( diff --git a/src/lib/sql/columns.sql b/src/lib/sql/columns.sql.ts similarity index 96% rename from src/lib/sql/columns.sql rename to src/lib/sql/columns.sql.ts index ad01e22a..66991ab4 100644 --- a/src/lib/sql/columns.sql +++ b/src/lib/sql/columns.sql.ts @@ -1,3 +1,4 @@ +export const COLUMNS_SQL = (schemaFilter?: string) => /* SQL */ ` -- Adapted from information_schema.columns SELECT @@ -97,6 +98,7 @@ FROM ORDER BY table_id, ordinal_position, oid asc ) AS check_constraints ON check_constraints.table_id = c.oid AND check_constraints.ordinal_position = a.attnum WHERE + ${schemaFilter ? `nc.nspname ${schemaFilter} AND` : ''} NOT pg_is_other_temp_schema(nc.oid) AND a.attnum > 0 AND NOT a.attisdropped @@ -109,3 +111,4 @@ WHERE 'SELECT, INSERT, UPDATE, REFERENCES' ) ) +` diff --git a/src/lib/sql/foreign_tables.sql.ts b/src/lib/sql/foreign_tables.sql.ts new file mode 100644 index 00000000..4785ba47 --- /dev/null +++ b/src/lib/sql/foreign_tables.sql.ts @@ -0,0 +1,13 @@ +export const FOREIGN_TABLES_SQL = (schemaFilter?: string) => /* SQL */ ` +SELECT + c.oid :: int8 AS id, + n.nspname AS schema, + c.relname AS name, + obj_description(c.oid) AS comment +FROM + pg_class c + JOIN pg_namespace n ON n.oid = c.relnamespace +WHERE + ${schemaFilter ? `n.nspname ${schemaFilter} AND` : ''} + c.relkind = 'f' +` diff --git a/src/lib/sql/functions.sql.ts b/src/lib/sql/functions.sql.ts new file mode 100644 index 00000000..7a74854b --- /dev/null +++ b/src/lib/sql/functions.sql.ts @@ -0,0 +1,111 @@ +export const FUNCTIONS_SQL = (schemaFilter?: string) => /* SQL */ ` +-- CTE with sane arg_modes, arg_names, and arg_types. +-- All three are always of the same length. +-- All three include all args, including OUT and TABLE args. +with functions as ( + select + *, + -- proargmodes is null when all arg modes are IN + coalesce( + p.proargmodes, + array_fill('i'::text, array[cardinality(coalesce(p.proallargtypes, p.proargtypes))]) + ) as arg_modes, + -- proargnames is null when all args are unnamed + coalesce( + p.proargnames, + array_fill(''::text, array[cardinality(coalesce(p.proallargtypes, p.proargtypes))]) + ) as arg_names, + -- proallargtypes is null when all arg modes are IN + coalesce(p.proallargtypes, p.proargtypes) as arg_types, + array_cat( + array_fill(false, array[pronargs - pronargdefaults]), + array_fill(true, array[pronargdefaults])) as arg_has_defaults + from + pg_proc as p + ${schemaFilter ? `join pg_namespace n on p.pronamespace = n.oid` : ''} + where + ${schemaFilter ? `n.nspname ${schemaFilter} AND` : ''} + p.prokind = 'f' +) +select + f.oid::int8 as id, + n.nspname as schema, + f.proname as name, + l.lanname as language, + case + when l.lanname = 'internal' then '' + else f.prosrc + end as definition, + case + when l.lanname = 'internal' then f.prosrc + else pg_get_functiondef(f.oid) + end as complete_statement, + coalesce(f_args.args, '[]') as args, + pg_get_function_arguments(f.oid) as argument_types, + pg_get_function_identity_arguments(f.oid) as identity_argument_types, + f.prorettype::int8 as return_type_id, + pg_get_function_result(f.oid) as return_type, + nullif(rt.typrelid::int8, 0) as return_type_relation_id, + f.proretset as is_set_returning_function, + case + when f.provolatile = 'i' then 'IMMUTABLE' + when f.provolatile = 's' then 'STABLE' + when f.provolatile = 'v' then 'VOLATILE' + end as behavior, + f.prosecdef as security_definer, + f_config.config_params as config_params +from + functions f + left join pg_namespace n on f.pronamespace = n.oid + left join pg_language l on f.prolang = l.oid + left join pg_type rt on rt.oid = f.prorettype + left join ( + select + oid, + jsonb_object_agg(param, value) filter (where param is not null) as config_params + from + ( + select + oid, + (string_to_array(unnest(proconfig), '='))[1] as param, + (string_to_array(unnest(proconfig), '='))[2] as value + from + functions + ) as t + group by + oid + ) f_config on f_config.oid = f.oid + left join ( + select + oid, + jsonb_agg(jsonb_build_object( + 'mode', t2.mode, + 'name', name, + 'type_id', type_id, + 'has_default', has_default + )) as args + from + ( + select + oid, + unnest(arg_modes) as mode, + unnest(arg_names) as name, + unnest(arg_types)::int8 as type_id, + unnest(arg_has_defaults) as has_default + from + functions + ) as t1, + lateral ( + select + case + when t1.mode = 'i' then 'in' + when t1.mode = 'o' then 'out' + when t1.mode = 'b' then 'inout' + when t1.mode = 'v' then 'variadic' + else 'table' + end as mode + ) as t2 + group by + t1.oid + ) f_args on f_args.oid = f.oid +` diff --git a/src/lib/sql/index.ts b/src/lib/sql/index.ts index 64be3aa8..1ad0bfb2 100644 --- a/src/lib/sql/index.ts +++ b/src/lib/sql/index.ts @@ -1,10 +1,12 @@ import { readFile } from 'node:fs/promises' import { dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' +import { TABLES_SQL } from './table.sql.js' +import { COLUMNS_SQL } from './columns.sql.js' const __dirname = dirname(fileURLToPath(import.meta.url)) export const columnPrivilegesSql = await readFile(join(__dirname, 'column_privileges.sql'), 'utf-8') -export const columnsSql = await readFile(join(__dirname, 'columns.sql'), 'utf-8') +export const columnsSql = COLUMNS_SQL() export const configSql = await readFile(join(__dirname, 'config.sql'), 'utf-8') export const extensionsSql = await readFile(join(__dirname, 'extensions.sql'), 'utf-8') export const foreignTablesSql = await readFile(join(__dirname, 'foreign_tables.sql'), 'utf-8') @@ -23,7 +25,7 @@ export const tableRelationshipsSql = await readFile( export const rolesSql = await readFile(join(__dirname, 'roles.sql'), 'utf-8') export const schemasSql = await readFile(join(__dirname, 'schemas.sql'), 'utf-8') export const tablePrivilegesSql = await readFile(join(__dirname, 'table_privileges.sql'), 'utf-8') -export const tablesSql = await readFile(join(__dirname, 'tables.sql'), 'utf-8') +export const tablesSql = TABLES_SQL() export const triggersSql = await readFile(join(__dirname, 'triggers.sql'), 'utf-8') export const typesSql = await readFile(join(__dirname, 'types.sql'), 'utf-8') export const versionSql = await readFile(join(__dirname, 'version.sql'), 'utf-8') diff --git a/src/lib/sql/materialized_views.sql.ts b/src/lib/sql/materialized_views.sql.ts new file mode 100644 index 00000000..3b4adce7 --- /dev/null +++ b/src/lib/sql/materialized_views.sql.ts @@ -0,0 +1,14 @@ +export const MATERIALIZED_VIEWS_SQL = (schemaFilter?: string) => /* SQL */ ` +select + c.oid::int8 as id, + n.nspname as schema, + c.relname as name, + c.relispopulated as is_populated, + obj_description(c.oid) as comment +from + pg_class c + join pg_namespace n on n.oid = c.relnamespace +where + ${schemaFilter ? `n.nspname ${schemaFilter} AND` : ''} + c.relkind = 'm' +` diff --git a/src/lib/sql/tables.sql b/src/lib/sql/table.sql.ts similarity index 90% rename from src/lib/sql/tables.sql rename to src/lib/sql/table.sql.ts index d0bb9df3..756a61e4 100644 --- a/src/lib/sql/tables.sql +++ b/src/lib/sql/table.sql.ts @@ -1,3 +1,4 @@ +export const TABLES_SQL = (schemaFilter?: string) => /* SQL */ ` SELECT c.oid :: int8 AS id, nc.nspname AS schema, @@ -41,6 +42,7 @@ FROM pg_attribute a, pg_namespace n where + ${schemaFilter ? `n.nspname ${schemaFilter} AND` : ''} i.indrelid = c.oid and c.relnamespace = n.oid and a.attrelid = c.oid @@ -73,11 +75,14 @@ FROM join pg_namespace nta on cta.relnamespace = nta.oid ) on ta.attrelid = c.confrelid and ta.attnum = any (c.confkey) where + ${schemaFilter ? `nsa.nspname ${schemaFilter} AND` : ''} + ${schemaFilter ? `nta.nspname ${schemaFilter} AND` : ''} c.contype = 'f' ) as relationships on (relationships.source_schema = nc.nspname and relationships.source_table_name = c.relname) or (relationships.target_table_schema = nc.nspname and relationships.target_table_name = c.relname) WHERE + ${schemaFilter ? `nc.nspname ${schemaFilter} AND` : ''} c.relkind IN ('r', 'p') AND NOT pg_is_other_temp_schema(nc.oid) AND ( @@ -96,3 +101,4 @@ group by c.relreplident, nc.nspname, pk.primary_keys +` diff --git a/src/lib/sql/views.sql.ts b/src/lib/sql/views.sql.ts new file mode 100644 index 00000000..aa304836 --- /dev/null +++ b/src/lib/sql/views.sql.ts @@ -0,0 +1,15 @@ +export const VIEWS_SQL = (schemaFilter?: string) => /* SQL */ ` +SELECT + c.oid :: int8 AS id, + n.nspname AS schema, + c.relname AS name, + -- See definition of information_schema.views + (pg_relation_is_updatable(c.oid, false) & 20) = 20 AS is_updatable, + obj_description(c.oid) AS comment +FROM + pg_class c + JOIN pg_namespace n ON n.oid = c.relnamespace +WHERE + ${schemaFilter ? `n.nspname ${schemaFilter} AND` : ''} + c.relkind = 'v' +` diff --git a/src/server/templates/typescript.ts b/src/server/templates/typescript.ts index 4f9cac03..7b91b43d 100644 --- a/src/server/templates/typescript.ts +++ b/src/server/templates/typescript.ts @@ -26,13 +26,17 @@ export const apply = async ({ detectOneToOneRelationships: boolean postgrestVersion?: string }): Promise => { + const start = Date.now() + console.log('applyTypescriptTemplate start: ', start) const columnsByTableId = Object.fromEntries( [...tables, ...foreignTables, ...views, ...materializedViews].map((t) => [t.id, []]) ) columns .filter((c) => c.table_id in columnsByTableId) .sort(({ name: a }, { name: b }) => a.localeCompare(b)) - .forEach((c) => columnsByTableId[c.table_id].push(c)) + .forEach((c) => { + columnsByTableId[c.table_id].push(c) + }) const internal_supabase_schema = postgrestVersion ? `// Allows to automatically instantiate createClient with right options @@ -578,6 +582,9 @@ export const Constants = { parser: 'typescript', semi: false, }) + const end = Date.now() + console.log('applyTypescriptTemplate end: ', end) + console.log('elapsedTypescriptTemplate: ', end - start) return output } From c4e1fb12cc5d1b6edd17f45b664568fc231813fa Mon Sep 17 00:00:00 2001 From: avallete Date: Fri, 29 Aug 2025 16:30:00 +0200 Subject: [PATCH 2/7] wip: refactor queries for root filtering --- schema-filter-demo.ts | 1 + src/lib/PostgresMetaColumnPrivileges.ts | 37 ++---- src/lib/PostgresMetaColumns.ts | 30 ++--- src/lib/PostgresMetaConfig.ts | 10 +- src/lib/PostgresMetaExtensions.ts | 14 +-- src/lib/PostgresMetaForeignTables.ts | 43 ++++--- src/lib/PostgresMetaFunctions.ts | 45 +++----- src/lib/PostgresMetaIndexes.ts | 30 +---- src/lib/PostgresMetaMaterializedViews.ts | 50 ++++---- src/lib/PostgresMetaPolicies.ts | 28 ++--- src/lib/PostgresMetaPublications.ts | 18 +-- src/lib/PostgresMetaRelationships.ts | 39 ++++--- src/lib/PostgresMetaRoles.ts | 14 +-- src/lib/PostgresMetaSchemas.ts | 17 ++- src/lib/PostgresMetaTablePrivileges.ts | 32 ++---- src/lib/PostgresMetaTables.ts | 27 +++-- src/lib/PostgresMetaTriggers.ts | 29 +---- src/lib/PostgresMetaTypes.ts | 38 +------ src/lib/PostgresMetaVersion.ts | 4 +- src/lib/PostgresMetaViews.ts | 50 ++++---- src/lib/generators.ts | 43 +------ src/lib/helpers.ts | 7 ++ ...rivileges.sql => column_privileges.sql.ts} | 20 +++- src/lib/sql/columns.sql.ts | 19 +++- src/lib/sql/{config.sql => config.sql.ts} | 6 + src/lib/sql/extensions.sql | 10 -- src/lib/sql/extensions.sql.ts | 19 ++++ src/lib/sql/foreign_tables.sql | 10 -- src/lib/sql/foreign_tables.sql.ts | 16 ++- src/lib/sql/functions.sql | 107 ------------------ src/lib/sql/functions.sql.ts | 18 ++- src/lib/sql/index.ts | 76 +++++++------ src/lib/sql/{indexes.sql => indexes.sql.ts} | 11 +- src/lib/sql/materialized_views.sql | 11 -- src/lib/sql/materialized_views.sql.ts | 11 +- src/lib/sql/{policies.sql => policies.sql.ts} | 12 ++ .../{publications.sql => publications.sql.ts} | 8 ++ src/lib/sql/{roles.sql => roles.sql.ts} | 8 ++ src/lib/sql/{schemas.sql => schemas.sql.ts} | 10 ++ src/lib/sql/table.sql.ts | 21 +++- ...privileges.sql => table_privileges.sql.ts} | 12 +- ...onships.sql => table_relationships.sql.ts} | 5 + src/lib/sql/{triggers.sql => triggers.sql.ts} | 15 ++- src/lib/sql/{types.sql => types.sql.ts} | 32 ++++++ src/lib/sql/{version.sql => version.sql.ts} | 3 + src/lib/sql/views.sql | 12 -- src/lib/sql/views.sql.ts | 9 +- ...cies.sql => views_key_dependencies.sql.ts} | 36 +++--- src/server/templates/typescript.ts | 5 - test/index.test.ts | 34 +++--- 50 files changed, 520 insertions(+), 642 deletions(-) create mode 100644 schema-filter-demo.ts rename src/lib/sql/{column_privileges.sql => column_privileges.sql.ts} (88%) rename src/lib/sql/{config.sql => config.sql.ts} (57%) delete mode 100644 src/lib/sql/extensions.sql create mode 100644 src/lib/sql/extensions.sql.ts delete mode 100644 src/lib/sql/foreign_tables.sql delete mode 100644 src/lib/sql/functions.sql rename src/lib/sql/{indexes.sql => indexes.sql.ts} (79%) delete mode 100644 src/lib/sql/materialized_views.sql rename src/lib/sql/{policies.sql => policies.sql.ts} (66%) rename src/lib/sql/{publications.sql => publications.sql.ts} (71%) rename src/lib/sql/{roles.sql => roles.sql.ts} (69%) rename src/lib/sql/{schemas.sql => schemas.sql.ts} (50%) rename src/lib/sql/{table_privileges.sql => table_privileges.sql.ts} (79%) rename src/lib/sql/{table_relationships.sql => table_relationships.sql.ts} (84%) rename src/lib/sql/{triggers.sql => triggers.sql.ts} (70%) rename src/lib/sql/{types.sql => types.sql.ts} (50%) rename src/lib/sql/{version.sql => version.sql.ts} (84%) delete mode 100644 src/lib/sql/views.sql rename src/lib/sql/{views_key_dependencies.sql => views_key_dependencies.sql.ts} (80%) diff --git a/schema-filter-demo.ts b/schema-filter-demo.ts new file mode 100644 index 00000000..0519ecba --- /dev/null +++ b/schema-filter-demo.ts @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/lib/PostgresMetaColumnPrivileges.ts b/src/lib/PostgresMetaColumnPrivileges.ts index 4df0d39a..51f306c6 100644 --- a/src/lib/PostgresMetaColumnPrivileges.ts +++ b/src/lib/PostgresMetaColumnPrivileges.ts @@ -1,7 +1,7 @@ import { ident, literal } from 'pg-format' import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' -import { filterByList } from './helpers.js' -import { columnPrivilegesSql } from './sql/index.js' +import { filterByValue, filterByList } from './helpers.js' +import { COLUMN_PRIVILEGES_SQL } from './sql/column_privileges.sql.js' import { PostgresMetaResult, PostgresColumnPrivileges, @@ -29,25 +29,12 @@ export default class PostgresMetaColumnPrivileges { limit?: number offset?: number } = {}): Promise> { - let sql = ` -with column_privileges as (${columnPrivilegesSql}) -select * -from column_privileges -` - const filter = filterByList( + const schemaFilter = filterByList( includedSchemas, excludedSchemas, !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined ) - if (filter) { - sql += ` where relation_schema ${filter}` - } - if (limit) { - sql += ` limit ${limit}` - } - if (offset) { - sql += ` offset ${offset}` - } + const sql = COLUMN_PRIVILEGES_SQL({ schemaFilter, limit, offset }) return await this.query(sql) } @@ -86,12 +73,8 @@ end $$; // Return the updated column privileges for modified columns. const columnIds = [...new Set(grants.map(({ column_id }) => column_id))] - sql = ` -with column_privileges as (${columnPrivilegesSql}) -select * -from column_privileges -where column_id in (${columnIds.map(literal).join(',')}) -` + const columnIdsFilter = filterByValue(columnIds) + sql = COLUMN_PRIVILEGES_SQL({ schemaFilter: undefined, columnIdsFilter }) return await this.query(sql) } @@ -130,12 +113,8 @@ end $$; // Return the updated column privileges for modified columns. const columnIds = [...new Set(revokes.map(({ column_id }) => column_id))] - sql = ` -with column_privileges as (${columnPrivilegesSql}) -select * -from column_privileges -where column_id in (${columnIds.map(literal).join(',')}) -` + const columnIdsFilter = filterByValue(columnIds) + sql = COLUMN_PRIVILEGES_SQL({ schemaFilter: undefined, columnIdsFilter }) return await this.query(sql) } } diff --git a/src/lib/PostgresMetaColumns.ts b/src/lib/PostgresMetaColumns.ts index f888baff..613c8ea2 100644 --- a/src/lib/PostgresMetaColumns.ts +++ b/src/lib/PostgresMetaColumns.ts @@ -2,7 +2,7 @@ import { ident, literal } from 'pg-format' import PostgresMetaTables from './PostgresMetaTables.js' import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' import { PostgresMetaResult, PostgresColumn } from './types.js' -import { filterByList } from './helpers.js' +import { filterByValue, filterByList } from './helpers.js' import { COLUMNS_SQL } from './sql/columns.sql.js' export default class PostgresMetaColumns { @@ -34,24 +34,8 @@ export default class PostgresMetaColumns { excludedSchemas, !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined ) - let sql = ` -WITH - columns AS (${COLUMNS_SQL(schemaFilter)}) -SELECT - * -FROM - columns -WHERE - true` - if (tableId !== undefined) { - sql += ` AND table_id = ${literal(tableId)}` - } - if (limit) { - sql += ` LIMIT ${limit}` - } - if (offset) { - sql += ` OFFSET ${offset}` - } + const tableIdFilter = tableId ? filterByValue([`${tableId}`]) : undefined + const sql = COLUMNS_SQL({ schemaFilter, tableIdFilter, limit, offset }) return await this.query(sql) } @@ -84,7 +68,8 @@ WHERE } const matches = id.match(regexp) as RegExpMatchArray const [tableId, ordinalPos] = matches.slice(1).map(Number) - const sql = `${COLUMNS_SQL(schemaFilter)} AND c.oid = ${tableId} AND a.attnum = ${ordinalPos};` + const idsFilter = filterByValue([`${tableId}.${ordinalPos}`]) + const sql = COLUMNS_SQL({ idsFilter }) const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -94,9 +79,8 @@ WHERE return { data: data[0], error } } } else if (name && table) { - const sql = `${COLUMNS_SQL(schemaFilter)} AND a.attname = ${literal(name)} AND c.relname = ${literal( - table - )};` + const columnNameFilter = filterByValue([`${table}.${name}`]) + const sql = `${COLUMNS_SQL({ schemaFilter, columnNameFilter })};` const { data, error } = await this.query(sql) if (error) { return { data, error } diff --git a/src/lib/PostgresMetaConfig.ts b/src/lib/PostgresMetaConfig.ts index d362641b..bf267076 100644 --- a/src/lib/PostgresMetaConfig.ts +++ b/src/lib/PostgresMetaConfig.ts @@ -1,4 +1,4 @@ -import { configSql } from './sql/index.js' +import { CONFIG_SQL } from './sql/index.js' import { PostgresMetaResult, PostgresConfig } from './types.js' export default class PostgresMetaConfig { @@ -15,13 +15,7 @@ export default class PostgresMetaConfig { limit?: number offset?: number } = {}): Promise> { - let sql = configSql - if (limit) { - sql = `${sql} LIMIT ${limit}` - } - if (offset) { - sql = `${sql} OFFSET ${offset}` - } + let sql = CONFIG_SQL({ limit, offset }) return await this.query(sql) } } diff --git a/src/lib/PostgresMetaExtensions.ts b/src/lib/PostgresMetaExtensions.ts index 4589057f..aeb54a1c 100644 --- a/src/lib/PostgresMetaExtensions.ts +++ b/src/lib/PostgresMetaExtensions.ts @@ -1,6 +1,7 @@ import { ident, literal } from 'pg-format' -import { extensionsSql } from './sql/index.js' import { PostgresMetaResult, PostgresExtension } from './types.js' +import { EXTENSIONS_SQL } from './sql/index.js' +import { filterByValue } from './helpers.js' export default class PostgresMetaExtensions { query: (sql: string) => Promise> @@ -16,18 +17,13 @@ export default class PostgresMetaExtensions { limit?: number offset?: number } = {}): Promise> { - let sql = extensionsSql - if (limit) { - sql = `${sql} LIMIT ${limit}` - } - if (offset) { - sql = `${sql} OFFSET ${offset}` - } + const sql = EXTENSIONS_SQL({ limit, offset }) return await this.query(sql) } async retrieve({ name }: { name: string }): Promise> { - const sql = `${extensionsSql} WHERE name = ${literal(name)};` + const nameFilter = filterByValue([`${name}`]) + const sql = EXTENSIONS_SQL({ nameFilter }) const { data, error } = await this.query(sql) if (error) { return { data, error } diff --git a/src/lib/PostgresMetaForeignTables.ts b/src/lib/PostgresMetaForeignTables.ts index 40ed859f..c7688201 100644 --- a/src/lib/PostgresMetaForeignTables.ts +++ b/src/lib/PostgresMetaForeignTables.ts @@ -1,7 +1,7 @@ -import { literal } from 'pg-format' -import { coalesceRowsToArray, filterByList } from './helpers.js' -import { columnsSql, foreignTablesSql } from './sql/index.js' +import { coalesceRowsToArray, filterByList, filterByValue } from './helpers.js' import { PostgresMetaResult, PostgresForeignTable } from './types.js' +import { FOREIGN_TABLES_SQL } from './sql/foreign_tables.sql.js' +import { COLUMNS_SQL } from './sql/columns.sql.js' export default class PostgresMetaForeignTables { query: (sql: string) => Promise> @@ -37,11 +37,8 @@ export default class PostgresMetaForeignTables { offset?: number includeColumns?: boolean } = {}): Promise> { - let sql = generateEnrichedForeignTablesSql({ includeColumns }) const filter = filterByList(includedSchemas, excludedSchemas) - if (filter) { - sql += ` where schema ${filter}` - } + let sql = generateEnrichedForeignTablesSql({ includeColumns, schemaFilter: filter }) if (limit) { sql += ` limit ${limit}` } @@ -68,10 +65,13 @@ export default class PostgresMetaForeignTables { name?: string schema?: string }): Promise> { + const schemaFilter = schema ? filterByList([schema], []) : undefined if (id) { - const sql = `${generateEnrichedForeignTablesSql({ + const idsFilter = filterByValue([`${id}`]) + const sql = generateEnrichedForeignTablesSql({ includeColumns: true, - })} where foreign_tables.id = ${literal(id)};` + idsFilter, + }) const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -81,11 +81,12 @@ export default class PostgresMetaForeignTables { return { data: data[0], error } } } else if (name) { - const sql = `${generateEnrichedForeignTablesSql({ + const nameFilter = filterByValue([`${schema}.${name}`]) + const sql = generateEnrichedForeignTablesSql({ includeColumns: true, - })} where foreign_tables.name = ${literal(name)} and foreign_tables.schema = ${literal( - schema - )};` + schemaFilter, + tableIdentifierFilter: nameFilter, + }) const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -103,9 +104,19 @@ export default class PostgresMetaForeignTables { } } -const generateEnrichedForeignTablesSql = ({ includeColumns }: { includeColumns: boolean }) => ` -with foreign_tables as (${foreignTablesSql}) - ${includeColumns ? `, columns as (${columnsSql})` : ''} +const generateEnrichedForeignTablesSql = ({ + includeColumns, + schemaFilter, + idsFilter, + tableIdentifierFilter, +}: { + includeColumns: boolean + schemaFilter?: string + idsFilter?: string + tableIdentifierFilter?: string +}) => ` +with foreign_tables as (${FOREIGN_TABLES_SQL({ schemaFilter, tableIdentifierFilter })}) + ${includeColumns ? `, columns as (${COLUMNS_SQL({ schemaFilter, tableIdentifierFilter, tableIdFilter: idsFilter })})` : ''} select * ${ diff --git a/src/lib/PostgresMetaFunctions.ts b/src/lib/PostgresMetaFunctions.ts index b50e6761..c3dcc44b 100644 --- a/src/lib/PostgresMetaFunctions.ts +++ b/src/lib/PostgresMetaFunctions.ts @@ -1,8 +1,8 @@ import { ident, literal } from 'pg-format' import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' -import { filterByList } from './helpers.js' -import { functionsSql } from './sql/index.js' +import { filterByList, filterByValue } from './helpers.js' import { PostgresMetaResult, PostgresFunction, PostgresFunctionCreate } from './types.js' +import { FUNCTIONS_SQL } from './sql/index.js' export default class PostgresMetaFunctions { query: (sql: string) => Promise> @@ -24,21 +24,12 @@ export default class PostgresMetaFunctions { limit?: number offset?: number } = {}): Promise> { - let sql = enrichedFunctionsSql - const filter = filterByList( + const schemaFilter = filterByList( includedSchemas, excludedSchemas, !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined ) - if (filter) { - sql += ` WHERE schema ${filter}` - } - if (limit) { - sql = `${sql} LIMIT ${limit}` - } - if (offset) { - sql = `${sql} OFFSET ${offset}` - } + let sql = FUNCTIONS_SQL({ schemaFilter, limit, offset }) return await this.query(sql) } @@ -63,8 +54,10 @@ export default class PostgresMetaFunctions { schema?: string args?: string[] }): Promise> { + const schemaFilter = schema ? filterByList([schema], []) : undefined if (id) { - const sql = `${enrichedFunctionsSql} WHERE id = ${literal(id)};` + const idsFilter = filterByValue([`${id}`]) + const sql = FUNCTIONS_SQL({ idsFilter }) const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -74,7 +67,8 @@ export default class PostgresMetaFunctions { return { data: data[0], error } } } else if (name && schema && args) { - const sql = this.generateRetrieveFunctionSql({ name, schema, args }) + const nameFilter = filterByValue([name]) + const sql = this.generateRetrieveFunctionSql({ schemaFilter, nameFilter, schema, name, args }) const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -177,7 +171,7 @@ export default class PostgresMetaFunctions { IF ( SELECT id - FROM (${functionsSql}) AS f + FROM (${FUNCTIONS_SQL({})}) AS f WHERE f.schema = ${literal(currentFunc!.schema)} AND f.name = ${literal(currentFunc!.name)} AND f.identity_argument_types = ${literal(identityArgs)} @@ -264,17 +258,17 @@ export default class PostgresMetaFunctions { } private generateRetrieveFunctionSql({ - schema, - name, + schemaFilter, + nameFilter, args, }: { + schemaFilter?: string + nameFilter?: string schema: string name: string args: string[] }): string { - return `${enrichedFunctionsSql} JOIN pg_proc AS p ON id = p.oid WHERE schema = ${literal( - schema - )} AND name = ${literal(name)} AND p.proargtypes::text = ${ + return `${FUNCTIONS_SQL({ schemaFilter, nameFilter })} JOIN pg_proc AS p ON f.oid = p.oid WHERE p.proargtypes::text = ${ args.length ? `( SELECT STRING_AGG(type_oid::text, ' ') FROM ( @@ -299,12 +293,3 @@ export default class PostgresMetaFunctions { }` } } - -const enrichedFunctionsSql = ` - WITH f AS ( - ${functionsSql} - ) - SELECT - f.* - FROM f -` diff --git a/src/lib/PostgresMetaIndexes.ts b/src/lib/PostgresMetaIndexes.ts index 14ffbba7..de0d9a6b 100644 --- a/src/lib/PostgresMetaIndexes.ts +++ b/src/lib/PostgresMetaIndexes.ts @@ -1,10 +1,10 @@ -import { ident, literal } from 'pg-format' +import { literal } from 'pg-format' import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' import { filterByList } from './helpers.js' -import { indexesSql } from './sql/index.js' import { PostgresMetaResult, PostgresIndex } from './types.js' +import { INDEXES_SQL } from './sql/index.js' -export default class PostgresMetaFunctions { +export default class PostgresMetaIndexes { query: (sql: string) => Promise> constructor(query: (sql: string) => Promise>) { @@ -24,21 +24,12 @@ export default class PostgresMetaFunctions { limit?: number offset?: number } = {}): Promise> { - let sql = enrichedSql - const filter = filterByList( + const schemaFilter = filterByList( includedSchemas, excludedSchemas, !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined ) - if (filter) { - sql += ` WHERE schema ${filter}` - } - if (limit) { - sql = `${sql} LIMIT ${limit}` - } - if (offset) { - sql = `${sql} OFFSET ${offset}` - } + let sql = INDEXES_SQL({ schemaFilter, limit, offset }) return await this.query(sql) } @@ -60,7 +51,7 @@ export default class PostgresMetaFunctions { args?: string[] }): Promise> { if (id) { - const sql = `${enrichedSql} WHERE id = ${literal(id)};` + const sql = `${INDEXES_SQL({})} WHERE id = ${literal(id)};` const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -74,12 +65,3 @@ export default class PostgresMetaFunctions { } } } - -const enrichedSql = ` - WITH x AS ( - ${indexesSql} - ) - SELECT - x.* - FROM x -` diff --git a/src/lib/PostgresMetaMaterializedViews.ts b/src/lib/PostgresMetaMaterializedViews.ts index 7f1efac5..78d8d4de 100644 --- a/src/lib/PostgresMetaMaterializedViews.ts +++ b/src/lib/PostgresMetaMaterializedViews.ts @@ -1,7 +1,7 @@ import { literal } from 'pg-format' -import { coalesceRowsToArray, filterByList } from './helpers.js' -import { columnsSql, materializedViewsSql } from './sql/index.js' +import { filterByList, coalesceRowsToArray } from './helpers.js' import { PostgresMetaResult, PostgresMaterializedView } from './types.js' +import { MATERIALIZED_VIEWS_SQL, COLUMNS_SQL } from './sql/index.js' export default class PostgresMetaMaterializedViews { query: (sql: string) => Promise> @@ -10,20 +10,6 @@ export default class PostgresMetaMaterializedViews { this.query = query } - async list(options: { - includedSchemas?: string[] - excludedSchemas?: string[] - limit?: number - offset?: number - includeColumns: true - }): Promise> - async list(options?: { - includedSchemas?: string[] - excludedSchemas?: string[] - limit?: number - offset?: number - includeColumns?: boolean - }): Promise> async list({ includedSchemas, excludedSchemas, @@ -37,17 +23,8 @@ export default class PostgresMetaMaterializedViews { offset?: number includeColumns?: boolean } = {}): Promise> { - let sql = generateEnrichedMaterializedViewsSql({ includeColumns }) - const filter = filterByList(includedSchemas, excludedSchemas, undefined) - if (filter) { - sql += ` where schema ${filter}` - } - if (limit) { - sql += ` limit ${limit}` - } - if (offset) { - sql += ` offset ${offset}` - } + const schemaFilter = filterByList(includedSchemas, excludedSchemas, undefined) + let sql = generateEnrichedMaterializedViewsSql({ includeColumns, schemaFilter, limit, offset }) return await this.query(sql) } @@ -68,9 +45,11 @@ export default class PostgresMetaMaterializedViews { name?: string schema?: string }): Promise> { + const schemaFilter = schema ? filterByList([schema], []) : undefined if (id) { const sql = `${generateEnrichedMaterializedViewsSql({ includeColumns: true, + schemaFilter, })} where materialized_views.id = ${literal(id)};` const { data, error } = await this.query(sql) if (error) { @@ -83,6 +62,7 @@ export default class PostgresMetaMaterializedViews { } else if (name) { const sql = `${generateEnrichedMaterializedViewsSql({ includeColumns: true, + schemaFilter, })} where materialized_views.name = ${literal( name )} and materialized_views.schema = ${literal(schema)};` @@ -103,9 +83,19 @@ export default class PostgresMetaMaterializedViews { } } -const generateEnrichedMaterializedViewsSql = ({ includeColumns }: { includeColumns: boolean }) => ` -with materialized_views as (${materializedViewsSql}) - ${includeColumns ? `, columns as (${columnsSql})` : ''} +const generateEnrichedMaterializedViewsSql = ({ + includeColumns, + schemaFilter, + limit, + offset, +}: { + includeColumns: boolean + schemaFilter?: string + limit?: number + offset?: number +}) => ` +with materialized_views as (${MATERIALIZED_VIEWS_SQL({ schemaFilter, limit, offset })}) + ${includeColumns ? `, columns as (${COLUMNS_SQL({ schemaFilter, limit, offset })}` : ''} select * ${ diff --git a/src/lib/PostgresMetaPolicies.ts b/src/lib/PostgresMetaPolicies.ts index fa476c12..a75ab24a 100644 --- a/src/lib/PostgresMetaPolicies.ts +++ b/src/lib/PostgresMetaPolicies.ts @@ -1,8 +1,8 @@ -import { ident, literal } from 'pg-format' +import { ident } from 'pg-format' import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' -import { filterByList } from './helpers.js' -import { policiesSql } from './sql/index.js' +import { filterByList, filterByValue } from './helpers.js' import { PostgresMetaResult, PostgresPolicy } from './types.js' +import { POLICIES_SQL } from './sql/index.js' export default class PostgresMetaPolicies { query: (sql: string) => Promise> @@ -24,21 +24,12 @@ export default class PostgresMetaPolicies { limit?: number offset?: number } = {}): Promise> { - let sql = policiesSql - const filter = filterByList( + const schemaFilter = filterByList( includedSchemas, excludedSchemas, !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined ) - if (filter) { - sql += ` WHERE n.nspname ${filter}` - } - if (limit) { - sql = `${sql} LIMIT ${limit}` - } - if (offset) { - sql = `${sql} OFFSET ${offset}` - } + let sql = POLICIES_SQL({ schemaFilter, limit, offset }) return await this.query(sql) } @@ -63,8 +54,10 @@ export default class PostgresMetaPolicies { table?: string schema?: string }): Promise> { + const schemaFilter = schema ? filterByList([schema], []) : undefined if (id) { - const sql = `${policiesSql} WHERE pol.oid = ${literal(id)};` + const idsFilter = filterByValue([`${id}`]) + const sql = POLICIES_SQL({ idsFilter }) const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -74,9 +67,8 @@ export default class PostgresMetaPolicies { return { data: data[0], error } } } else if (name && table) { - const sql = `${policiesSql} WHERE pol.polname = ${literal(name)} AND n.nspname = ${literal( - schema - )} AND c.relname = ${literal(table)};` + const functionNameIdentifierFilter = filterByValue([`${table}.${name}`]) + const sql = POLICIES_SQL({ schemaFilter, functionNameIdentifierFilter }) const { data, error } = await this.query(sql) if (error) { return { data, error } diff --git a/src/lib/PostgresMetaPublications.ts b/src/lib/PostgresMetaPublications.ts index 63c1bafe..4cedfb19 100644 --- a/src/lib/PostgresMetaPublications.ts +++ b/src/lib/PostgresMetaPublications.ts @@ -1,6 +1,6 @@ import { ident, literal } from 'pg-format' -import { publicationsSql } from './sql/index.js' -import { PostgresMetaResult, PostgresPublication, PostgresTable } from './types.js' +import { PostgresMetaResult, PostgresPublication } from './types.js' +import { PUBLICATIONS_SQL } from './sql/index.js' export default class PostgresMetaPublications { query: (sql: string) => Promise> @@ -16,13 +16,7 @@ export default class PostgresMetaPublications { limit?: number offset?: number }): Promise> { - let sql = publicationsSql - if (limit) { - sql = `${sql} LIMIT ${limit}` - } - if (offset) { - sql = `${sql} OFFSET ${offset}` - } + let sql = PUBLICATIONS_SQL({ limit, offset }) return await this.query(sql) } @@ -36,7 +30,7 @@ export default class PostgresMetaPublications { name?: string }): Promise> { if (id) { - const sql = `${publicationsSql} WHERE p.oid = ${literal(id)};` + const sql = `${PUBLICATIONS_SQL({})} WHERE p.oid = ${literal(id)};` const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -46,7 +40,7 @@ export default class PostgresMetaPublications { return { data: data[0], error } } } else if (name) { - const sql = `${publicationsSql} WHERE p.pubname = ${literal(name)};` + const sql = `${PUBLICATIONS_SQL({})} WHERE p.pubname = ${literal(name)};` const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -223,7 +217,7 @@ begin create temp table pg_meta_publication_tmp (name) on commit drop as values (coalesce(new_name, old.pubname)); end $$; -with publications as (${publicationsSql}) select * from publications where name = (select name from pg_meta_publication_tmp); +with publications as (${PUBLICATIONS_SQL({})}) select * from publications where name = (select name from pg_meta_publication_tmp); ` const { data, error } = await this.query(sql) if (error) { diff --git a/src/lib/PostgresMetaRelationships.ts b/src/lib/PostgresMetaRelationships.ts index 059762c3..e71ce556 100644 --- a/src/lib/PostgresMetaRelationships.ts +++ b/src/lib/PostgresMetaRelationships.ts @@ -1,23 +1,37 @@ -import { literal } from 'pg-format' import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' -import { tableRelationshipsSql, viewsKeyDependenciesSql } from './sql/index.js' -import { PostgresMetaResult, PostgresRelationship } from './types.js' +import { filterByList } from './helpers.js' +import type { PostgresMetaResult, PostgresRelationship } from './types.js' +import { TABLE_RELATIONSHIPS_SQL } from './sql/table_relationships.sql.js' +import { VIEWS_KEY_DEPENDENCIES_SQL } from './sql/views_key_dependencies.sql.js' /* * Only used for generating types at the moment. Will need some cleanups before * using it for other things, e.g. /relationships endpoint. */ export default class PostgresMetaRelationships { - query: (sql: string) => Promise> + query: (sql: string) => Promise> - constructor(query: (sql: string) => Promise>) { + constructor(query: (sql: string) => Promise>) { this.query = query } - async list(): Promise> { + async list({ + includeSystemSchemas = false, + includedSchemas, + excludedSchemas, + }: { + includeSystemSchemas?: boolean + includedSchemas?: string[] + excludedSchemas?: string[] + } = {}): Promise> { + const filter = filterByList( + includedSchemas, + excludedSchemas, + !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined + ) let allTableM2oAndO2oRelationships: PostgresRelationship[] { - let sql = tableRelationshipsSql + const sql = TABLE_RELATIONSHIPS_SQL(filter) const { data, error } = (await this.query(sql)) as PostgresMetaResult if (error) { return { data: null, error } @@ -46,7 +60,7 @@ export default class PostgresMetaRelationships { } const { data: viewsKeyDependencies, error } = (await this.query( - allViewsKeyDependenciesSql + VIEWS_KEY_DEPENDENCIES_SQL(filter) )) as PostgresMetaResult if (error) { return { data: null, error } @@ -62,8 +76,8 @@ export default class PostgresMetaRelationships { return allEntries.reduce( (results, entries) => results - .map((result) => entries.map((entry) => [...result, entry])) - .reduce((subResults, result) => [...subResults, ...result], []), + .map((result) => entries.map((entry) => result.concat(entry))) + .reduce((subResults, result) => subResults.concat(result), []), [[]] ) } @@ -147,8 +161,3 @@ export default class PostgresMetaRelationships { } } } - -const allViewsKeyDependenciesSql = viewsKeyDependenciesSql.replaceAll( - '__EXCLUDED_SCHEMAS', - literal(DEFAULT_SYSTEM_SCHEMAS) -) diff --git a/src/lib/PostgresMetaRoles.ts b/src/lib/PostgresMetaRoles.ts index f55fb4a9..9c805fe5 100644 --- a/src/lib/PostgresMetaRoles.ts +++ b/src/lib/PostgresMetaRoles.ts @@ -1,5 +1,5 @@ import { ident, literal } from 'pg-format' -import { rolesSql } from './sql/index.js' +import { ROLES_SQL } from './sql/index.js' import { PostgresMetaResult, PostgresRole, @@ -34,7 +34,7 @@ export default class PostgresMetaRoles { } = {}): Promise> { let sql = ` WITH - roles AS (${rolesSql}) + roles AS (${ROLES_SQL({ limit, offset })}) SELECT * FROM @@ -52,12 +52,6 @@ WHERE // ``` sql += ` AND NOT pg_catalog.starts_with(name, 'pg_')` } - if (limit) { - sql += ` LIMIT ${limit}` - } - if (offset) { - sql += ` OFFSET ${offset}` - } const result = await this.query(sql) if (result.data) { result.data = result.data.map((role: any) => { @@ -78,7 +72,7 @@ WHERE name?: string }): Promise> { if (id) { - const sql = `${rolesSql} WHERE oid = ${literal(id)};` + const sql = `${ROLES_SQL({})} WHERE oid = ${literal(id)};` const { data, error } = await this.query(sql) if (error) { @@ -90,7 +84,7 @@ WHERE return { data: data[0], error } } } else if (name) { - const sql = `${rolesSql} WHERE rolname = ${literal(name)};` + const sql = `${ROLES_SQL({})} WHERE rolname = ${literal(name)};` const { data, error } = await this.query(sql) if (error) { return { data, error } diff --git a/src/lib/PostgresMetaSchemas.ts b/src/lib/PostgresMetaSchemas.ts index b84a64cc..ade585c5 100644 --- a/src/lib/PostgresMetaSchemas.ts +++ b/src/lib/PostgresMetaSchemas.ts @@ -1,12 +1,13 @@ import { ident, literal } from 'pg-format' import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' -import { schemasSql } from './sql/index.js' +import { SCHEMAS_SQL } from './sql/index.js' import { PostgresMetaResult, PostgresSchema, PostgresSchemaCreate, PostgresSchemaUpdate, } from './types.js' +import { filterByValue } from './helpers.js' export default class PostgresMetaSchemas { query: (sql: string) => Promise> @@ -24,16 +25,10 @@ export default class PostgresMetaSchemas { limit?: number offset?: number } = {}): Promise> { - let sql = schemasSql + let sql = SCHEMAS_SQL({ limit, offset }) if (!includeSystemSchemas) { sql = `${sql} AND NOT (n.nspname IN (${DEFAULT_SYSTEM_SCHEMAS.map(literal).join(',')}))` } - if (limit) { - sql = `${sql} LIMIT ${limit}` - } - if (offset) { - sql = `${sql} OFFSET ${offset}` - } return await this.query(sql) } @@ -47,7 +42,8 @@ export default class PostgresMetaSchemas { name?: string }): Promise> { if (id) { - const sql = `${schemasSql} AND n.oid = ${literal(id)};` + const idsFilter = filterByValue([`${id}`]) + const sql = SCHEMAS_SQL({ idsFilter }) const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -57,7 +53,8 @@ export default class PostgresMetaSchemas { return { data: data[0], error } } } else if (name) { - const sql = `${schemasSql} AND n.nspname = ${literal(name)};` + const nameFilter = filterByValue([`${name}`]) + const sql = SCHEMAS_SQL({ nameFilter }) const { data, error } = await this.query(sql) if (error) { return { data, error } diff --git a/src/lib/PostgresMetaTablePrivileges.ts b/src/lib/PostgresMetaTablePrivileges.ts index 9edb32e9..d960ba58 100644 --- a/src/lib/PostgresMetaTablePrivileges.ts +++ b/src/lib/PostgresMetaTablePrivileges.ts @@ -1,13 +1,13 @@ import { ident, literal } from 'pg-format' import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' import { filterByList } from './helpers.js' -import { tablePrivilegesSql } from './sql/index.js' import { PostgresMetaResult, PostgresTablePrivileges, PostgresTablePrivilegesGrant, PostgresTablePrivilegesRevoke, } from './types.js' +import { TABLE_PRIVILEGES_SQL } from './sql/index.js' export default class PostgresMetaTablePrivileges { query: (sql: string) => Promise> @@ -29,25 +29,16 @@ export default class PostgresMetaTablePrivileges { limit?: number offset?: number } = {}): Promise> { - let sql = ` -with table_privileges as (${tablePrivilegesSql}) -select * -from table_privileges -` - const filter = filterByList( + const schemaFilter = filterByList( includedSchemas, excludedSchemas, !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined ) - if (filter) { - sql += ` where schema ${filter}` - } - if (limit) { - sql += ` limit ${limit}` - } - if (offset) { - sql += ` offset ${offset}` - } + let sql = ` +with table_privileges as (${TABLE_PRIVILEGES_SQL({ schemaFilter, limit, offset })}) +select * +from table_privileges +` return await this.query(sql) } @@ -68,9 +59,10 @@ from table_privileges name?: string schema?: string }): Promise> { + const schemaFilter = schema ? filterByList([schema], []) : undefined if (id) { const sql = ` -with table_privileges as (${tablePrivilegesSql}) +with table_privileges as (${TABLE_PRIVILEGES_SQL({ schemaFilter })}) select * from table_privileges where table_privileges.relation_id = ${literal(id)};` @@ -84,7 +76,7 @@ where table_privileges.relation_id = ${literal(id)};` } } else if (name) { const sql = ` -with table_privileges as (${tablePrivilegesSql}) +with table_privileges as (${TABLE_PRIVILEGES_SQL({ schemaFilter })}) select * from table_privileges where table_privileges.schema = ${literal(schema)} @@ -130,7 +122,7 @@ end $$; // Return the updated table privileges for modified relations. const relationIds = [...new Set(grants.map(({ relation_id }) => relation_id))] sql = ` -with table_privileges as (${tablePrivilegesSql}) +with table_privileges as (${TABLE_PRIVILEGES_SQL({})}) select * from table_privileges where relation_id in (${relationIds.map(literal).join(',')}) @@ -160,7 +152,7 @@ end $$; // Return the updated table privileges for modified relations. const relationIds = [...new Set(revokes.map(({ relation_id }) => relation_id))] sql = ` -with table_privileges as (${tablePrivilegesSql}) +with table_privileges as (${TABLE_PRIVILEGES_SQL({})}) select * from table_privileges where relation_id in (${relationIds.map(literal).join(',')}) diff --git a/src/lib/PostgresMetaTables.ts b/src/lib/PostgresMetaTables.ts index 0d7e277a..7efa123d 100644 --- a/src/lib/PostgresMetaTables.ts +++ b/src/lib/PostgresMetaTables.ts @@ -1,14 +1,13 @@ import { ident, literal } from 'pg-format' import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' -import { coalesceRowsToArray, filterByList } from './helpers.js' +import { coalesceRowsToArray, filterByValue, filterByList } from './helpers.js' import { PostgresMetaResult, PostgresTable, PostgresTableCreate, PostgresTableUpdate, } from './types.js' -import { TABLES_SQL } from './sql/table.sql.js' -import { COLUMNS_SQL } from './sql/columns.sql.js' +import { TABLES_SQL, COLUMNS_SQL } from './sql/index.js' export default class PostgresMetaTables { query: (sql: string) => Promise> @@ -48,22 +47,18 @@ export default class PostgresMetaTables { offset?: number includeColumns?: boolean } = {}): Promise> { - const filter = filterByList( + const schemaFilter = filterByList( includedSchemas, excludedSchemas, !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined ) - let sql = generateEnrichedTablesSql({ includeColumns, schemaFilter: filter }) - if (filter) { - sql += ` where schema ${filter}` - } + let sql = generateEnrichedTablesSql({ includeColumns, schemaFilter }) if (limit) { sql += ` limit ${limit}` } if (offset) { sql += ` offset ${offset}` } - console.log('sql tables: ', sql) return await this.query(sql) } @@ -86,9 +81,11 @@ export default class PostgresMetaTables { }): Promise> { const schemaFilter = schema ? filterByList([schema], []) : undefined if (id) { + const idsFilter = filterByValue([`${id}`]) const sql = `${generateEnrichedTablesSql({ schemaFilter, includeColumns: true, + idsFilter, })} where tables.id = ${literal(id)};` const { data, error } = await this.query(sql) if (error) { @@ -99,10 +96,12 @@ export default class PostgresMetaTables { return { data: data[0], error } } } else if (name) { + const tableIdentifierFilter = filterByValue([`${schema}.${name}`]) const sql = `${generateEnrichedTablesSql({ schemaFilter, includeColumns: true, - })} where tables.name = ${literal(name)} and tables.schema = ${literal(schema)};` + tableIdentifierFilter, + })}` const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -255,12 +254,16 @@ COMMIT;` const generateEnrichedTablesSql = ({ includeColumns, schemaFilter, + tableIdentifierFilter, + idsFilter, }: { includeColumns: boolean schemaFilter?: string + tableIdentifierFilter?: string + idsFilter?: string }) => ` -with tables as (${TABLES_SQL(schemaFilter)}) - ${includeColumns ? `, columns as (${COLUMNS_SQL(schemaFilter)})` : ''} +with tables as (${TABLES_SQL({ schemaFilter, tableIdentifierFilter, idsFilter })}) + ${includeColumns ? `, columns as (${COLUMNS_SQL({ schemaFilter, tableIdFilter: idsFilter })})` : ''} select * ${includeColumns ? `, ${coalesceRowsToArray('columns', 'columns.table_id = tables.id')}` : ''} diff --git a/src/lib/PostgresMetaTriggers.ts b/src/lib/PostgresMetaTriggers.ts index 5ce05f76..fa792924 100644 --- a/src/lib/PostgresMetaTriggers.ts +++ b/src/lib/PostgresMetaTriggers.ts @@ -1,8 +1,8 @@ import { ident, literal } from 'pg-format' import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' import { filterByList } from './helpers.js' -import { triggersSql } from './sql/index.js' import { PostgresMetaResult, PostgresTrigger } from './types.js' +import { TRIGGERS_SQL } from './sql/index.js' export default class PostgresMetaTriggers { query: (sql: string) => Promise> @@ -24,21 +24,12 @@ export default class PostgresMetaTriggers { limit?: number offset?: number } = {}): Promise> { - let sql = enrichedTriggersSql - const filter = filterByList( + const schemaFilter = filterByList( includedSchemas, excludedSchemas, !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined ) - if (filter) { - sql += ` WHERE schema ${filter}` - } - if (limit) { - sql = `${sql} LIMIT ${limit}` - } - if (offset) { - sql = `${sql} OFFSET ${offset}` - } + let sql = TRIGGERS_SQL({ schemaFilter, limit, offset }) return await this.query(sql) } @@ -63,8 +54,9 @@ export default class PostgresMetaTriggers { schema?: string table?: string }): Promise> { + const schemaFilter = schema ? filterByList([schema], []) : undefined if (id) { - const sql = `${enrichedTriggersSql} WHERE id = ${literal(id)};` + const sql = `${TRIGGERS_SQL({ schemaFilter })} WHERE id = ${literal(id)};` const { data, error } = await this.query(sql) @@ -82,7 +74,7 @@ export default class PostgresMetaTriggers { } if (name && schema && table) { - const sql = `${enrichedTriggersSql} WHERE name = ${literal(name)} AND schema = ${literal( + const sql = `${TRIGGERS_SQL({ schemaFilter })} WHERE name = ${literal(name)} AND schema = ${literal( schema )} AND triggers.table = ${literal(table)};` @@ -254,12 +246,3 @@ export default class PostgresMetaTriggers { return { data: triggerRecord!, error: null } } } - -const enrichedTriggersSql = ` - WITH triggers AS ( - ${triggersSql} - ) - SELECT - * - FROM triggers -` diff --git a/src/lib/PostgresMetaTypes.ts b/src/lib/PostgresMetaTypes.ts index 35371d55..1608fe14 100644 --- a/src/lib/PostgresMetaTypes.ts +++ b/src/lib/PostgresMetaTypes.ts @@ -1,7 +1,7 @@ import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' import { filterByList } from './helpers.js' -import { typesSql } from './sql/index.js' import { PostgresMetaResult, PostgresType } from './types.js' +import { TYPES_SQL } from './sql/index.js' export default class PostgresMetaTypes { query: (sql: string) => Promise> @@ -27,44 +27,12 @@ export default class PostgresMetaTypes { limit?: number offset?: number } = {}): Promise> { - let sql = `${typesSql} - where - ( - t.typrelid = 0 - or ( - select - c.relkind ${includeTableTypes ? `in ('c', 'r')` : `= 'c'`} - from - pg_class c - where - c.oid = t.typrelid - ) - ) - ` - if (!includeArrayTypes) { - sql += ` and not exists ( - select - from - pg_type el - where - el.oid = t.typelem - and el.typarray = t.oid - )` - } - const filter = filterByList( + const schemaFilter = filterByList( includedSchemas, excludedSchemas, !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined ) - if (filter) { - sql += ` and n.nspname ${filter}` - } - if (limit) { - sql += ` limit ${limit}` - } - if (offset) { - sql += ` offset ${offset}` - } + let sql = TYPES_SQL({ schemaFilter, limit, offset, includeTableTypes, includeArrayTypes }) return await this.query(sql) } } diff --git a/src/lib/PostgresMetaVersion.ts b/src/lib/PostgresMetaVersion.ts index 38e0299f..55865a37 100644 --- a/src/lib/PostgresMetaVersion.ts +++ b/src/lib/PostgresMetaVersion.ts @@ -1,4 +1,4 @@ -import { versionSql } from './sql/index.js' +import { VERSION_SQL } from './sql/index.js' import { PostgresMetaResult, PostgresVersion } from './types.js' export default class PostgresMetaVersion { @@ -9,7 +9,7 @@ export default class PostgresMetaVersion { } async retrieve(): Promise> { - const { data, error } = await this.query(versionSql) + const { data, error } = await this.query(VERSION_SQL()) if (error) { return { data, error } } diff --git a/src/lib/PostgresMetaViews.ts b/src/lib/PostgresMetaViews.ts index 0f6ad09c..e57d380d 100644 --- a/src/lib/PostgresMetaViews.ts +++ b/src/lib/PostgresMetaViews.ts @@ -1,8 +1,8 @@ import { literal } from 'pg-format' import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' import { coalesceRowsToArray, filterByList } from './helpers.js' -import { columnsSql, viewsSql } from './sql/index.js' import { PostgresMetaResult, PostgresView } from './types.js' +import { VIEWS_SQL, COLUMNS_SQL } from './sql/index.js' export default class PostgresMetaViews { query: (sql: string) => Promise> @@ -11,22 +11,6 @@ export default class PostgresMetaViews { this.query = query } - async list(options: { - includeSystemSchemas?: boolean - includedSchemas?: string[] - excludedSchemas?: string[] - limit?: number - offset?: number - includeColumns: false - }): Promise> - async list(options?: { - includeSystemSchemas?: boolean - includedSchemas?: string[] - excludedSchemas?: string[] - limit?: number - offset?: number - includeColumns?: boolean - }): Promise> async list({ includeSystemSchemas = false, includedSchemas, @@ -42,21 +26,12 @@ export default class PostgresMetaViews { offset?: number includeColumns?: boolean } = {}): Promise> { - let sql = generateEnrichedViewsSql({ includeColumns }) - const filter = filterByList( + const schemaFilter = filterByList( includedSchemas, excludedSchemas, !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined ) - if (filter) { - sql += ` where schema ${filter}` - } - if (limit) { - sql += ` limit ${limit}` - } - if (offset) { - sql += ` offset ${offset}` - } + let sql = generateEnrichedViewsSql({ includeColumns, schemaFilter, limit, offset }) return await this.query(sql) } @@ -77,9 +52,11 @@ export default class PostgresMetaViews { name?: string schema?: string }): Promise> { + const schemaFilter = schema ? filterByList([schema], []) : undefined if (id) { const sql = `${generateEnrichedViewsSql({ includeColumns: true, + schemaFilter, })} where views.id = ${literal(id)};` const { data, error } = await this.query(sql) if (error) { @@ -92,6 +69,7 @@ export default class PostgresMetaViews { } else if (name) { const sql = `${generateEnrichedViewsSql({ includeColumns: true, + schemaFilter, })} where views.name = ${literal(name)} and views.schema = ${literal(schema)};` const { data, error } = await this.query(sql) if (error) { @@ -110,9 +88,19 @@ export default class PostgresMetaViews { } } -const generateEnrichedViewsSql = ({ includeColumns }: { includeColumns: boolean }) => ` -with views as (${viewsSql}) - ${includeColumns ? `, columns as (${columnsSql})` : ''} +const generateEnrichedViewsSql = ({ + includeColumns, + schemaFilter, + limit, + offset, +}: { + includeColumns: boolean + schemaFilter?: string + limit?: number + offset?: number +}) => ` +with views as (${VIEWS_SQL({ schemaFilter, limit, offset })}) + ${includeColumns ? `, columns as (${COLUMNS_SQL({ schemaFilter, limit, offset })}` : ''} select * ${includeColumns ? `, ${coalesceRowsToArray('columns', 'columns.table_id = views.id')}` : ''} diff --git a/src/lib/generators.ts b/src/lib/generators.ts index 2d729636..a5f6419f 100644 --- a/src/lib/generators.ts +++ b/src/lib/generators.ts @@ -31,8 +31,6 @@ export async function getGeneratorMetadata( excludedSchemas: [], } ): Promise> { - const start = Date.now() - console.log('getGeneratorMetadata start: ') const includedSchemas = filters.includedSchemas ?? [] const excludedSchemas = filters.excludedSchemas ?? [] @@ -50,8 +48,6 @@ export async function getGeneratorMetadata( return { data: null, error: tablesError } } - const startForeignTables = Date.now() - console.log('getGeneratorMetadata foreignTables start: ', startForeignTables) const { data: foreignTables, error: foreignTablesError } = await pgMeta.foreignTables.list({ includedSchemas: includedSchemas.length > 0 ? includedSchemas : undefined, excludedSchemas, @@ -60,12 +56,7 @@ export async function getGeneratorMetadata( if (foreignTablesError) { return { data: null, error: foreignTablesError } } - const endForeignTables = Date.now() - console.log('getGeneratorMetadata foreignTables end: ', endForeignTables) - console.log('elapsedForeignTables: ', endForeignTables - startForeignTables) - const startViews = Date.now() - console.log('getGeneratorMetadata views start: ', startViews) const { data: views, error: viewsError } = await pgMeta.views.list({ includedSchemas: includedSchemas.length > 0 ? includedSchemas : undefined, excludedSchemas, @@ -84,12 +75,7 @@ export async function getGeneratorMetadata( if (materializedViewsError) { return { data: null, error: materializedViewsError } } - const endViews = Date.now() - console.log('getGeneratorMetadata views end: ', endViews) - console.log('elapsedViews: ', endViews - startViews) - const startColumns = Date.now() - console.log('getGeneratorMetadata columns start: ', startColumns) const { data: columns, error: columnsError } = await pgMeta.columns.list({ includedSchemas: includedSchemas.length > 0 ? includedSchemas : undefined, excludedSchemas, @@ -97,22 +83,15 @@ export async function getGeneratorMetadata( if (columnsError) { return { data: null, error: columnsError } } - const endColumns = Date.now() - console.log('getGeneratorMetadata columns end: ', endColumns) - console.log('elapsedColumns: ', endColumns - startColumns) - const startRelationships = Date.now() - console.log('getGeneratorMetadata relationships start: ', startRelationships) - const { data: relationships, error: relationshipsError } = await pgMeta.relationships.list() + const { data: relationships, error: relationshipsError } = await pgMeta.relationships.list({ + includedSchemas: includedSchemas.length > 0 ? includedSchemas : undefined, + excludedSchemas, + }) if (relationshipsError) { return { data: null, error: relationshipsError } } - const endRelationships = Date.now() - console.log('getGeneratorMetadata relationships end: ', endRelationships) - console.log('elapsedRelationships: ', endRelationships - startRelationships) - const startFunctions = Date.now() - console.log('getGeneratorMetadata functions start: ', startFunctions) const { data: functions, error: functionsError } = await pgMeta.functions.list({ includedSchemas: includedSchemas.length > 0 ? includedSchemas : undefined, excludedSchemas, @@ -120,13 +99,10 @@ export async function getGeneratorMetadata( if (functionsError) { return { data: null, error: functionsError } } - const endFunctions = Date.now() - console.log('getGeneratorMetadata functions end: ', endFunctions) - console.log('elapsedFunctions: ', endFunctions - startFunctions) - const startTypes = Date.now() - console.log('getGeneratorMetadata types start: ', startTypes) const { data: types, error: typesError } = await pgMeta.types.list({ + includedSchemas: includedSchemas.length > 0 ? includedSchemas : undefined, + excludedSchemas, includeTableTypes: true, includeArrayTypes: true, includeSystemSchemas: true, @@ -134,16 +110,9 @@ export async function getGeneratorMetadata( if (typesError) { return { data: null, error: typesError } } - const endTypes = Date.now() - console.log('getGeneratorMetadata types end: ', endTypes) - console.log('elapsedTypes: ', endTypes - startTypes) await pgMeta.end() - const end = Date.now() - console.log('getGeneratorMetadata end: ', end) - console.log('elapsed: ', end - start) - return { data: { schemas: schemas.filter( diff --git a/src/lib/helpers.ts b/src/lib/helpers.ts index 7145bb40..57939e5c 100644 --- a/src/lib/helpers.ts +++ b/src/lib/helpers.ts @@ -24,3 +24,10 @@ export const filterByList = (include?: string[], exclude?: string[], defaultExcl } return '' } + +export const filterByValue = (ids?: string[]) => { + if (ids?.length) { + return `IN (${ids.map(literal).join(',')})` + } + return '' +} diff --git a/src/lib/sql/column_privileges.sql b/src/lib/sql/column_privileges.sql.ts similarity index 88% rename from src/lib/sql/column_privileges.sql rename to src/lib/sql/column_privileges.sql.ts index 8540c583..1886c1b4 100644 --- a/src/lib/sql/column_privileges.sql +++ b/src/lib/sql/column_privileges.sql.ts @@ -1,3 +1,10 @@ +import type { SQLQueryPropsWithSchemaFilter } from './index.js' + +export const COLUMN_PRIVILEGES_SQL = ( + props: SQLQueryPropsWithSchemaFilter & { + columnIdsFilter?: string + } +) => /* SQL */ ` -- Lists each column's privileges in the form of: -- -- [ @@ -28,8 +35,8 @@ -- - we include column privileges for materialized views -- (reason for exclusion in information_schema.column_privileges: -- https://www.postgresql.org/message-id/9136.1502740844%40sss.pgh.pa.us) --- - we query a.attrelid and a.attnum to generate `column_id` --- - `table_catalog` is omitted +-- - we query a.attrelid and a.attnum to generate \`column_id\` +-- - \`table_catalog\` is omitted -- - table_schema -> relation_schema, table_name -> relation_name -- -- Column privileges are intertwined with table privileges in that table @@ -37,12 +44,12 @@ -- -- grant all on mytable to myrole; -- --- Then `myrole` is granted privileges for ALL columns. Likewise, if we do: +-- Then \`myrole\` is granted privileges for ALL columns. Likewise, if we do: -- -- grant all (id) on mytable to myrole; -- revoke all on mytable from myrole; -- --- Then the grant on the `id` column is revoked. +-- Then the grant on the \`id\` column is revoked. -- -- This is unlike how grants for schemas and tables interact, where you need -- privileges for BOTH the schema the table is in AND the table itself in order @@ -130,6 +137,8 @@ from union all select (0)::oid as oid, 'PUBLIC') grantee(oid, rolname) where ((x.relnamespace = nc.oid) + ${props.schemaFilter ? `and nc.nspname ${props.schemaFilter}` : ''} + ${props.columnIdsFilter ? `and (x.attrelid || '.' || x.attnum) ${props.columnIdsFilter}` : ''} and (x.grantee = grantee.oid) and (x.grantor = u_grantor.oid) and (x.prtype = any (ARRAY['INSERT', @@ -143,3 +152,6 @@ group by column_id, nc.nspname, x.relname, x.attname +${props.limit ? `limit ${props.limit}` : ''} +${props.offset ? `offset ${props.offset}` : ''} +` diff --git a/src/lib/sql/columns.sql.ts b/src/lib/sql/columns.sql.ts index 66991ab4..ecf91e81 100644 --- a/src/lib/sql/columns.sql.ts +++ b/src/lib/sql/columns.sql.ts @@ -1,4 +1,13 @@ -export const COLUMNS_SQL = (schemaFilter?: string) => /* SQL */ ` +import type { SQLQueryPropsWithSchemaFilter } from './index.js' + +export const COLUMNS_SQL = ( + props: SQLQueryPropsWithSchemaFilter & { + tableIdFilter?: string + tableIdentifierFilter?: string + columnNameFilter?: string + idsFilter?: string + } +) => /* SQL */ ` -- Adapted from information_schema.columns SELECT @@ -98,7 +107,11 @@ FROM ORDER BY table_id, ordinal_position, oid asc ) AS check_constraints ON check_constraints.table_id = c.oid AND check_constraints.ordinal_position = a.attnum WHERE - ${schemaFilter ? `nc.nspname ${schemaFilter} AND` : ''} + ${props.schemaFilter ? `nc.nspname ${props.schemaFilter} AND` : ''} + ${props.idsFilter ? `(c.oid || '.' || a.attnum) ${props.idsFilter} AND` : ''} + ${props.columnNameFilter ? `(c.relname || '.' || a.attname) ${props.columnNameFilter} AND` : ''} + ${props.tableIdFilter ? `c.oid ${props.tableIdFilter} AND` : ''} + ${props.tableIdentifierFilter ? `nc.nspname || '.' || c.relname ${props.tableIdentifierFilter} AND` : ''} NOT pg_is_other_temp_schema(nc.oid) AND a.attnum > 0 AND NOT a.attisdropped @@ -111,4 +124,6 @@ WHERE 'SELECT, INSERT, UPDATE, REFERENCES' ) ) +${props.limit ? `limit ${props.limit}` : ''} +${props.offset ? `offset ${props.offset}` : ''} ` diff --git a/src/lib/sql/config.sql b/src/lib/sql/config.sql.ts similarity index 57% rename from src/lib/sql/config.sql rename to src/lib/sql/config.sql.ts index 553e4426..ed524079 100644 --- a/src/lib/sql/config.sql +++ b/src/lib/sql/config.sql.ts @@ -1,3 +1,6 @@ +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './index.js' + +export const CONFIG_SQL = (props: SQLQueryPropsWithSchemaFilterAndIdsFilter) => /* SQL */ ` SELECT name, setting, @@ -23,3 +26,6 @@ FROM ORDER BY category, name +${props.limit ? `limit ${props.limit}` : ''} +${props.offset ? `offset ${props.offset}` : ''} +` diff --git a/src/lib/sql/extensions.sql b/src/lib/sql/extensions.sql deleted file mode 100644 index 9a8700f8..00000000 --- a/src/lib/sql/extensions.sql +++ /dev/null @@ -1,10 +0,0 @@ -SELECT - e.name, - n.nspname AS schema, - e.default_version, - x.extversion AS installed_version, - e.comment -FROM - pg_available_extensions() e(name, default_version, comment) - LEFT JOIN pg_extension x ON e.name = x.extname - LEFT JOIN pg_namespace n ON x.extnamespace = n.oid diff --git a/src/lib/sql/extensions.sql.ts b/src/lib/sql/extensions.sql.ts new file mode 100644 index 00000000..0e389ca7 --- /dev/null +++ b/src/lib/sql/extensions.sql.ts @@ -0,0 +1,19 @@ +import type { SQLQueryProps } from './index.js' + +export const EXTENSIONS_SQL = (props: SQLQueryProps & { nameFilter?: string }) => /* SQL */ ` +SELECT + e.name, + n.nspname AS schema, + e.default_version, + x.extversion AS installed_version, + e.comment +FROM + pg_available_extensions() e(name, default_version, comment) + LEFT JOIN pg_extension x ON e.name = x.extname + LEFT JOIN pg_namespace n ON x.extnamespace = n.oid +WHERE + true + ${props.nameFilter ? `AND e.name ${props.nameFilter}` : ''} +${props.limit ? `limit ${props.limit}` : ''} +${props.offset ? `offset ${props.offset}` : ''} +` diff --git a/src/lib/sql/foreign_tables.sql b/src/lib/sql/foreign_tables.sql deleted file mode 100644 index e3e5e14f..00000000 --- a/src/lib/sql/foreign_tables.sql +++ /dev/null @@ -1,10 +0,0 @@ -SELECT - c.oid :: int8 AS id, - n.nspname AS schema, - c.relname AS name, - obj_description(c.oid) AS comment -FROM - pg_class c - JOIN pg_namespace n ON n.oid = c.relnamespace -WHERE - c.relkind = 'f' diff --git a/src/lib/sql/foreign_tables.sql.ts b/src/lib/sql/foreign_tables.sql.ts index 4785ba47..e848835b 100644 --- a/src/lib/sql/foreign_tables.sql.ts +++ b/src/lib/sql/foreign_tables.sql.ts @@ -1,4 +1,12 @@ -export const FOREIGN_TABLES_SQL = (schemaFilter?: string) => /* SQL */ ` +import type { SQLQueryProps } from './index.js' + +export const FOREIGN_TABLES_SQL = ( + props: SQLQueryProps & { + schemaFilter?: string + idsFilter?: string + tableIdentifierFilter?: string + } +) => /* SQL */ ` SELECT c.oid :: int8 AS id, n.nspname AS schema, @@ -8,6 +16,10 @@ FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE - ${schemaFilter ? `n.nspname ${schemaFilter} AND` : ''} + ${props.schemaFilter ? `n.nspname ${props.schemaFilter} AND` : ''} + ${props.idsFilter ? `c.oid ${props.idsFilter} AND` : ''} + ${props.tableIdentifierFilter ? `(n.nspname || '.' || c.relname) ${props.tableIdentifierFilter} AND` : ''} c.relkind = 'f' +${props.limit ? `limit ${props.limit}` : ''} +${props.offset ? `offset ${props.offset}` : ''} ` diff --git a/src/lib/sql/functions.sql b/src/lib/sql/functions.sql deleted file mode 100644 index d2258402..00000000 --- a/src/lib/sql/functions.sql +++ /dev/null @@ -1,107 +0,0 @@ --- CTE with sane arg_modes, arg_names, and arg_types. --- All three are always of the same length. --- All three include all args, including OUT and TABLE args. -with functions as ( - select - *, - -- proargmodes is null when all arg modes are IN - coalesce( - p.proargmodes, - array_fill('i'::text, array[cardinality(coalesce(p.proallargtypes, p.proargtypes))]) - ) as arg_modes, - -- proargnames is null when all args are unnamed - coalesce( - p.proargnames, - array_fill(''::text, array[cardinality(coalesce(p.proallargtypes, p.proargtypes))]) - ) as arg_names, - -- proallargtypes is null when all arg modes are IN - coalesce(p.proallargtypes, p.proargtypes) as arg_types, - array_cat( - array_fill(false, array[pronargs - pronargdefaults]), - array_fill(true, array[pronargdefaults])) as arg_has_defaults - from - pg_proc as p - where - p.prokind = 'f' -) -select - f.oid::int8 as id, - n.nspname as schema, - f.proname as name, - l.lanname as language, - case - when l.lanname = 'internal' then '' - else f.prosrc - end as definition, - case - when l.lanname = 'internal' then f.prosrc - else pg_get_functiondef(f.oid) - end as complete_statement, - coalesce(f_args.args, '[]') as args, - pg_get_function_arguments(f.oid) as argument_types, - pg_get_function_identity_arguments(f.oid) as identity_argument_types, - f.prorettype::int8 as return_type_id, - pg_get_function_result(f.oid) as return_type, - nullif(rt.typrelid::int8, 0) as return_type_relation_id, - f.proretset as is_set_returning_function, - case - when f.provolatile = 'i' then 'IMMUTABLE' - when f.provolatile = 's' then 'STABLE' - when f.provolatile = 'v' then 'VOLATILE' - end as behavior, - f.prosecdef as security_definer, - f_config.config_params as config_params -from - functions f - left join pg_namespace n on f.pronamespace = n.oid - left join pg_language l on f.prolang = l.oid - left join pg_type rt on rt.oid = f.prorettype - left join ( - select - oid, - jsonb_object_agg(param, value) filter (where param is not null) as config_params - from - ( - select - oid, - (string_to_array(unnest(proconfig), '='))[1] as param, - (string_to_array(unnest(proconfig), '='))[2] as value - from - functions - ) as t - group by - oid - ) f_config on f_config.oid = f.oid - left join ( - select - oid, - jsonb_agg(jsonb_build_object( - 'mode', t2.mode, - 'name', name, - 'type_id', type_id, - 'has_default', has_default - )) as args - from - ( - select - oid, - unnest(arg_modes) as mode, - unnest(arg_names) as name, - unnest(arg_types)::int8 as type_id, - unnest(arg_has_defaults) as has_default - from - functions - ) as t1, - lateral ( - select - case - when t1.mode = 'i' then 'in' - when t1.mode = 'o' then 'out' - when t1.mode = 'b' then 'inout' - when t1.mode = 'v' then 'variadic' - else 'table' - end as mode - ) as t2 - group by - t1.oid - ) f_args on f_args.oid = f.oid diff --git a/src/lib/sql/functions.sql.ts b/src/lib/sql/functions.sql.ts index 7a74854b..9c124113 100644 --- a/src/lib/sql/functions.sql.ts +++ b/src/lib/sql/functions.sql.ts @@ -1,10 +1,16 @@ -export const FUNCTIONS_SQL = (schemaFilter?: string) => /* SQL */ ` +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './index.js' + +export const FUNCTIONS_SQL = ( + props: SQLQueryPropsWithSchemaFilterAndIdsFilter & { + nameFilter?: string + } +) => /* SQL */ ` -- CTE with sane arg_modes, arg_names, and arg_types. -- All three are always of the same length. -- All three include all args, including OUT and TABLE args. with functions as ( select - *, + p.*, -- proargmodes is null when all arg modes are IN coalesce( p.proargmodes, @@ -22,9 +28,11 @@ with functions as ( array_fill(true, array[pronargdefaults])) as arg_has_defaults from pg_proc as p - ${schemaFilter ? `join pg_namespace n on p.pronamespace = n.oid` : ''} + ${props.schemaFilter ? `join pg_namespace n on p.pronamespace = n.oid` : ''} where - ${schemaFilter ? `n.nspname ${schemaFilter} AND` : ''} + ${props.schemaFilter ? `n.nspname ${props.schemaFilter} AND` : ''} + ${props.idsFilter ? `p.oid::text ${props.idsFilter} AND` : ''} + ${props.nameFilter ? `p.proname ${props.nameFilter} AND` : ''} p.prokind = 'f' ) select @@ -108,4 +116,6 @@ from group by t1.oid ) f_args on f_args.oid = f.oid +${props.limit ? `limit ${props.limit}` : ''} +${props.offset ? `offset ${props.offset}` : ''} ` diff --git a/src/lib/sql/index.ts b/src/lib/sql/index.ts index 1ad0bfb2..9ed673f1 100644 --- a/src/lib/sql/index.ts +++ b/src/lib/sql/index.ts @@ -1,36 +1,42 @@ -import { readFile } from 'node:fs/promises' -import { dirname, join } from 'node:path' -import { fileURLToPath } from 'node:url' -import { TABLES_SQL } from './table.sql.js' -import { COLUMNS_SQL } from './columns.sql.js' +export type SQLQueryProps = { + limit?: number + offset?: number +} -const __dirname = dirname(fileURLToPath(import.meta.url)) -export const columnPrivilegesSql = await readFile(join(__dirname, 'column_privileges.sql'), 'utf-8') -export const columnsSql = COLUMNS_SQL() -export const configSql = await readFile(join(__dirname, 'config.sql'), 'utf-8') -export const extensionsSql = await readFile(join(__dirname, 'extensions.sql'), 'utf-8') -export const foreignTablesSql = await readFile(join(__dirname, 'foreign_tables.sql'), 'utf-8') -export const functionsSql = await readFile(join(__dirname, 'functions.sql'), 'utf-8') -export const indexesSql = await readFile(join(__dirname, 'indexes.sql'), 'utf-8') -export const materializedViewsSql = await readFile( - join(__dirname, 'materialized_views.sql'), - 'utf-8' -) -export const policiesSql = await readFile(join(__dirname, 'policies.sql'), 'utf-8') -export const publicationsSql = await readFile(join(__dirname, 'publications.sql'), 'utf-8') -export const tableRelationshipsSql = await readFile( - join(__dirname, 'table_relationships.sql'), - 'utf-8' -) -export const rolesSql = await readFile(join(__dirname, 'roles.sql'), 'utf-8') -export const schemasSql = await readFile(join(__dirname, 'schemas.sql'), 'utf-8') -export const tablePrivilegesSql = await readFile(join(__dirname, 'table_privileges.sql'), 'utf-8') -export const tablesSql = TABLES_SQL() -export const triggersSql = await readFile(join(__dirname, 'triggers.sql'), 'utf-8') -export const typesSql = await readFile(join(__dirname, 'types.sql'), 'utf-8') -export const versionSql = await readFile(join(__dirname, 'version.sql'), 'utf-8') -export const viewsKeyDependenciesSql = await readFile( - join(__dirname, 'views_key_dependencies.sql'), - 'utf-8' -) -export const viewsSql = await readFile(join(__dirname, 'views.sql'), 'utf-8') +export type SQLQueryPropsWithSchemaFilter = SQLQueryProps & { + schemaFilter?: string +} + +export type SQLQueryPropsWithIdsFilter = SQLQueryProps & { + idsFilter?: string +} + +export type SQLQueryPropsWithSchemaFilterAndIdsFilter = SQLQueryProps & { + schemaFilter?: string + idsFilter?: string +} + +export type SQLQueryPropsWithTypes = SQLQueryPropsWithSchemaFilterAndIdsFilter & { + includeTableTypes?: boolean + includeArrayTypes?: boolean +} + +// Export all SQL functions +export { VERSION_SQL } from './version.sql.js' +export { CONFIG_SQL } from './config.sql.js' +export { EXTENSIONS_SQL } from './extensions.sql.js' +export { PUBLICATIONS_SQL } from './publications.sql.js' +export { ROLES_SQL } from './roles.sql.js' +export { SCHEMAS_SQL } from './schemas.sql.js' +export { FUNCTIONS_SQL } from './functions.sql.js' +export { INDEXES_SQL } from './indexes.sql.js' +export { POLICIES_SQL } from './policies.sql.js' +export { TRIGGERS_SQL } from './triggers.sql.js' +export { TYPES_SQL } from './types.sql.js' +export { TABLES_SQL } from './table.sql.js' +export { VIEWS_SQL } from './views.sql.js' +export { MATERIALIZED_VIEWS_SQL } from './materialized_views.sql.js' +export { FOREIGN_TABLES_SQL } from './foreign_tables.sql.js' +export { COLUMNS_SQL } from './columns.sql.js' +export { TABLE_PRIVILEGES_SQL } from './table_privileges.sql.js' +export { COLUMN_PRIVILEGES_SQL } from './column_privileges.sql.js' diff --git a/src/lib/sql/indexes.sql b/src/lib/sql/indexes.sql.ts similarity index 79% rename from src/lib/sql/indexes.sql rename to src/lib/sql/indexes.sql.ts index ff0c8f36..cb8684de 100644 --- a/src/lib/sql/indexes.sql +++ b/src/lib/sql/indexes.sql.ts @@ -1,3 +1,6 @@ +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './index.js' + +export const INDEXES_SQL = (props: SQLQueryPropsWithSchemaFilterAndIdsFilter) => /* SQL */ ` SELECT idx.indexrelid::int8 AS id, idx.indrelid::int8 AS table_id, @@ -37,5 +40,11 @@ SELECT JOIN pg_am am ON c.relam = am.oid JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum = ANY(idx.indkey) JOIN pg_indexes ix ON c.relname = ix.indexname + WHERE + ${props.schemaFilter ? `n.nspname ${props.schemaFilter}` : 'true'} + ${props.idsFilter ? `AND idx.indexrelid ${props.idsFilter}` : ''} GROUP BY - idx.indexrelid, idx.indrelid, n.nspname, idx.indnatts, idx.indnkeyatts, idx.indisunique, idx.indisprimary, idx.indisexclusion, idx.indimmediate, idx.indisclustered, idx.indisvalid, idx.indcheckxmin, idx.indisready, idx.indislive, idx.indisreplident, idx.indkey, idx.indcollation, idx.indclass, idx.indoption, idx.indexprs, idx.indpred, ix.indexdef, am.amname \ No newline at end of file + idx.indexrelid, idx.indrelid, n.nspname, idx.indnatts, idx.indnkeyatts, idx.indisunique, idx.indisprimary, idx.indisexclusion, idx.indimmediate, idx.indisclustered, idx.indisvalid, idx.indcheckxmin, idx.indisready, idx.indislive, idx.indisreplident, idx.indkey, idx.indcollation, idx.indclass, idx.indoption, idx.indexprs, idx.indpred, ix.indexdef, am.amname +${props.limit ? `limit ${props.limit}` : ''} +${props.offset ? `offset ${props.offset}` : ''} +` diff --git a/src/lib/sql/materialized_views.sql b/src/lib/sql/materialized_views.sql deleted file mode 100644 index 5281f7da..00000000 --- a/src/lib/sql/materialized_views.sql +++ /dev/null @@ -1,11 +0,0 @@ -select - c.oid::int8 as id, - n.nspname as schema, - c.relname as name, - c.relispopulated as is_populated, - obj_description(c.oid) as comment -from - pg_class c - join pg_namespace n on n.oid = c.relnamespace -where - c.relkind = 'm' diff --git a/src/lib/sql/materialized_views.sql.ts b/src/lib/sql/materialized_views.sql.ts index 3b4adce7..8ea28396 100644 --- a/src/lib/sql/materialized_views.sql.ts +++ b/src/lib/sql/materialized_views.sql.ts @@ -1,4 +1,8 @@ -export const MATERIALIZED_VIEWS_SQL = (schemaFilter?: string) => /* SQL */ ` +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './index.js' + +export const MATERIALIZED_VIEWS_SQL = ( + props: SQLQueryPropsWithSchemaFilterAndIdsFilter +) => /* SQL */ ` select c.oid::int8 as id, n.nspname as schema, @@ -9,6 +13,9 @@ from pg_class c join pg_namespace n on n.oid = c.relnamespace where - ${schemaFilter ? `n.nspname ${schemaFilter} AND` : ''} + ${props.schemaFilter ? `n.nspname ${props.schemaFilter} AND` : ''} + ${props.idsFilter ? `c.oid ${props.idsFilter} AND` : ''} c.relkind = 'm' +${props.limit ? `limit ${props.limit}` : ''} +${props.offset ? `offset ${props.offset}` : ''} ` diff --git a/src/lib/sql/policies.sql b/src/lib/sql/policies.sql.ts similarity index 66% rename from src/lib/sql/policies.sql rename to src/lib/sql/policies.sql.ts index 20a09327..92cfb188 100644 --- a/src/lib/sql/policies.sql +++ b/src/lib/sql/policies.sql.ts @@ -1,3 +1,8 @@ +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './index.js' + +export const POLICIES_SQL = ( + props: SQLQueryPropsWithSchemaFilterAndIdsFilter & { functionNameIdentifierFilter?: string } +) => /* SQL */ ` SELECT pol.oid :: int8 AS id, n.nspname AS schema, @@ -40,3 +45,10 @@ FROM pg_policy pol JOIN pg_class c ON c.oid = pol.polrelid LEFT JOIN pg_namespace n ON n.oid = c.relnamespace +WHERE + ${props.schemaFilter ? `n.nspname ${props.schemaFilter}` : 'true'} + ${props.idsFilter ? `AND pol.oid ${props.idsFilter}` : ''} + ${props.functionNameIdentifierFilter ? `AND (c.relname || '.' || pol.polname) ${props.functionNameIdentifierFilter}` : ''} +${props.limit ? `limit ${props.limit}` : ''} +${props.offset ? `offset ${props.offset}` : ''} +` diff --git a/src/lib/sql/publications.sql b/src/lib/sql/publications.sql.ts similarity index 71% rename from src/lib/sql/publications.sql rename to src/lib/sql/publications.sql.ts index ed0a2e20..ee665394 100644 --- a/src/lib/sql/publications.sql +++ b/src/lib/sql/publications.sql.ts @@ -1,3 +1,6 @@ +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './index.js' + +export const PUBLICATIONS_SQL = (props: SQLQueryPropsWithSchemaFilterAndIdsFilter) => /* SQL */ ` SELECT p.oid :: int8 AS id, p.pubname AS name, @@ -34,3 +37,8 @@ FROM WHERE pr.prpubid = p.oid ) AS pr ON 1 = 1 +WHERE + ${props.idsFilter ? `p.oid ${props.idsFilter}` : 'true'} +${props.limit ? `limit ${props.limit}` : ''} +${props.offset ? `offset ${props.offset}` : ''} +` diff --git a/src/lib/sql/roles.sql b/src/lib/sql/roles.sql.ts similarity index 69% rename from src/lib/sql/roles.sql rename to src/lib/sql/roles.sql.ts index a0c79d6f..5b730ebc 100644 --- a/src/lib/sql/roles.sql +++ b/src/lib/sql/roles.sql.ts @@ -1,3 +1,6 @@ +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './index.js' + +export const ROLES_SQL = (props: SQLQueryPropsWithSchemaFilterAndIdsFilter) => /* SQL */ ` -- TODO: Consider using pg_authid vs. pg_roles for unencrypted password field SELECT oid :: int8 AS id, @@ -25,3 +28,8 @@ SELECT rolconfig AS config FROM pg_roles +WHERE + ${props.idsFilter ? `oid ${props.idsFilter}` : 'true'} +${props.limit ? `limit ${props.limit}` : ''} +${props.offset ? `offset ${props.offset}` : ''} +` diff --git a/src/lib/sql/schemas.sql b/src/lib/sql/schemas.sql.ts similarity index 50% rename from src/lib/sql/schemas.sql rename to src/lib/sql/schemas.sql.ts index a4859fff..57b6f9c6 100644 --- a/src/lib/sql/schemas.sql +++ b/src/lib/sql/schemas.sql.ts @@ -1,3 +1,8 @@ +import type { SQLQueryProps } from './index.js' + +export const SCHEMAS_SQL = ( + props: SQLQueryProps & { nameFilter?: string; idsFilter?: string } +) => /* SQL */ ` -- Adapted from information_schema.schemata select @@ -9,9 +14,14 @@ from pg_roles u where n.nspowner = u.oid + ${props.idsFilter ? `and n.oid ${props.idsFilter}` : ''} + ${props.nameFilter ? `and n.nspname ${props.nameFilter}` : ''} and ( pg_has_role(n.nspowner, 'USAGE') or has_schema_privilege(n.oid, 'CREATE, USAGE') ) and not pg_catalog.starts_with(n.nspname, 'pg_temp_') and not pg_catalog.starts_with(n.nspname, 'pg_toast_temp_') +${props.limit ? `limit ${props.limit}` : ''} +${props.offset ? `offset ${props.offset}` : ''} +` diff --git a/src/lib/sql/table.sql.ts b/src/lib/sql/table.sql.ts index 756a61e4..af5f7158 100644 --- a/src/lib/sql/table.sql.ts +++ b/src/lib/sql/table.sql.ts @@ -1,4 +1,8 @@ -export const TABLES_SQL = (schemaFilter?: string) => /* SQL */ ` +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './index.js' + +export const TABLES_SQL = ( + props: SQLQueryPropsWithSchemaFilterAndIdsFilter & { tableIdentifierFilter?: string } +) => /* SQL */ ` SELECT c.oid :: int8 AS id, nc.nspname AS schema, @@ -42,7 +46,8 @@ FROM pg_attribute a, pg_namespace n where - ${schemaFilter ? `n.nspname ${schemaFilter} AND` : ''} + ${props.schemaFilter ? `n.nspname ${props.schemaFilter} AND` : ''} + ${props.tableIdentifierFilter ? `n.nspname || '.' || c.relname ${props.tableIdentifierFilter} AND` : ''} i.indrelid = c.oid and c.relnamespace = n.oid and a.attrelid = c.oid @@ -75,14 +80,18 @@ FROM join pg_namespace nta on cta.relnamespace = nta.oid ) on ta.attrelid = c.confrelid and ta.attnum = any (c.confkey) where - ${schemaFilter ? `nsa.nspname ${schemaFilter} AND` : ''} - ${schemaFilter ? `nta.nspname ${schemaFilter} AND` : ''} + ${props.schemaFilter ? `nsa.nspname ${props.schemaFilter} AND` : ''} + ${props.schemaFilter ? `nta.nspname ${props.schemaFilter} AND` : ''} + ${props.tableIdentifierFilter ? `nsa.nspname || '.' || csa.relname ${props.tableIdentifierFilter} AND` : ''} + ${props.tableIdentifierFilter ? `nta.nspname || '.' || cta.relname ${props.tableIdentifierFilter} AND` : ''} c.contype = 'f' ) as relationships on (relationships.source_schema = nc.nspname and relationships.source_table_name = c.relname) or (relationships.target_table_schema = nc.nspname and relationships.target_table_name = c.relname) WHERE - ${schemaFilter ? `nc.nspname ${schemaFilter} AND` : ''} + ${props.schemaFilter ? `nc.nspname ${props.schemaFilter} AND` : ''} + ${props.idsFilter ? `c.oid ${props.idsFilter} AND` : ''} + ${props.tableIdentifierFilter ? `nc.nspname || '.' || c.relname ${props.tableIdentifierFilter} AND` : ''} c.relkind IN ('r', 'p') AND NOT pg_is_other_temp_schema(nc.oid) AND ( @@ -101,4 +110,6 @@ group by c.relreplident, nc.nspname, pk.primary_keys +${props.limit ? `limit ${props.limit}` : ''} +${props.offset ? `offset ${props.offset}` : ''} ` diff --git a/src/lib/sql/table_privileges.sql b/src/lib/sql/table_privileges.sql.ts similarity index 79% rename from src/lib/sql/table_privileges.sql rename to src/lib/sql/table_privileges.sql.ts index 435409dc..2726476c 100644 --- a/src/lib/sql/table_privileges.sql +++ b/src/lib/sql/table_privileges.sql.ts @@ -1,4 +1,9 @@ --- Despite the name `table_privileges`, this includes other kinds of relations: +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './index.js' + +export const TABLE_PRIVILEGES_SQL = ( + props: SQLQueryPropsWithSchemaFilterAndIdsFilter +) => /* SQL */ ` +-- Despite the name \`table_privileges\`, this includes other kinds of relations: -- views, matviews, etc. "Relation privileges" just doesn't roll off the tongue. -- -- For each relation, get its relacl in a jsonb format, @@ -59,6 +64,8 @@ left join ( ) as grantee (oid, rolname) on grantee.oid = _priv.grantee where c.relkind in ('r', 'v', 'm', 'f', 'p') + ${props.schemaFilter ? `and nc.nspname ${props.schemaFilter}` : ''} + ${props.idsFilter ? `and c.oid ${props.idsFilter}` : ''} and not pg_is_other_temp_schema(c.relnamespace) and ( pg_has_role(c.relowner, 'USAGE') @@ -73,3 +80,6 @@ group by nc.nspname, c.relname, c.relkind +${props.limit ? `limit ${props.limit}` : ''} +${props.offset ? `offset ${props.offset}` : ''} +` diff --git a/src/lib/sql/table_relationships.sql b/src/lib/sql/table_relationships.sql.ts similarity index 84% rename from src/lib/sql/table_relationships.sql rename to src/lib/sql/table_relationships.sql.ts index 53b80ded..e5909eb9 100644 --- a/src/lib/sql/table_relationships.sql +++ b/src/lib/sql/table_relationships.sql.ts @@ -1,3 +1,4 @@ +export const TABLE_RELATIONSHIPS_SQL = (schemaFilter?: string) => /* SQL */ ` -- Adapted from -- https://github.com/PostgREST/postgrest/blob/f9f0f79fa914ac00c11fbf7f4c558e14821e67e2/src/PostgREST/SchemaCache.hs#L722 WITH @@ -15,6 +16,7 @@ pks_uniques_cols AS ( WHERE contype IN ('p', 'u') and connamespace::regnamespace::text <> 'pg_catalog' + ${schemaFilter ? `and connamespace::regnamespace::text ${schemaFilter}` : ''} GROUP BY connamespace, conrelid ) SELECT @@ -34,6 +36,7 @@ JOIN LATERAL ( FROM unnest(traint.conkey, traint.confkey) WITH ORDINALITY AS _(col, ref, ord) JOIN pg_attribute cols ON cols.attrelid = traint.conrelid AND cols.attnum = col JOIN pg_attribute refs ON refs.attrelid = traint.confrelid AND refs.attnum = ref + WHERE ${schemaFilter ? `traint.connamespace::regnamespace::text ${schemaFilter}` : 'true'} ) AS column_info ON TRUE JOIN pg_namespace ns1 ON ns1.oid = traint.connamespace JOIN pg_class tab ON tab.oid = traint.conrelid @@ -42,3 +45,5 @@ JOIN pg_namespace ns2 ON ns2.oid = other.relnamespace LEFT JOIN pks_uniques_cols pks_uqs ON pks_uqs.connamespace = traint.connamespace AND pks_uqs.conrelid = traint.conrelid WHERE traint.contype = 'f' AND traint.conparentid = 0 +${schemaFilter ? `and ns1.nspname ${schemaFilter}` : ''} +` diff --git a/src/lib/sql/triggers.sql b/src/lib/sql/triggers.sql.ts similarity index 70% rename from src/lib/sql/triggers.sql rename to src/lib/sql/triggers.sql.ts index 09fcef14..17303fa7 100644 --- a/src/lib/sql/triggers.sql +++ b/src/lib/sql/triggers.sql.ts @@ -1,3 +1,6 @@ +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './index.js' + +export const TRIGGERS_SQL = (props: SQLQueryPropsWithSchemaFilterAndIdsFilter) => /* SQL */ ` SELECT pg_t.oid AS id, pg_t.tgrelid AS table_id, @@ -6,10 +9,10 @@ SELECT WHEN pg_t.tgenabled = 'O' THEN 'ORIGIN' WHEN pg_t.tgenabled = 'R' THEN 'REPLICA' WHEN pg_t.tgenabled = 'A' THEN 'ALWAYS' - END AS enabled_mode, + END AS enabled_mode, ( STRING_TO_ARRAY( - ENCODE(pg_t.tgargs, 'escape'), '\000' + ENCODE(pg_t.tgargs, 'escape'), '\x00' ) )[:pg_t.tgnargs] AS function_args, is_t.trigger_name AS name, @@ -26,6 +29,8 @@ FROM JOIN pg_class AS pg_c ON pg_t.tgrelid = pg_c.oid +JOIN pg_namespace AS table_ns +ON pg_c.relnamespace = table_ns.oid JOIN information_schema.triggers AS is_t ON is_t.trigger_name = pg_t.tgname AND pg_c.relname = is_t.event_object_table @@ -34,6 +39,9 @@ JOIN pg_proc AS pg_p ON pg_t.tgfoid = pg_p.oid JOIN pg_namespace AS pg_n ON pg_p.pronamespace = pg_n.oid +WHERE + ${props.schemaFilter ? `table_ns.nspname ${props.schemaFilter}` : 'true'} + ${props.idsFilter ? `AND pg_t.oid ${props.idsFilter}` : ''} GROUP BY pg_t.oid, pg_t.tgrelid, @@ -48,3 +56,6 @@ GROUP BY is_t.action_timing, pg_p.proname, pg_n.nspname +${props.limit ? `limit ${props.limit}` : ''} +${props.offset ? `offset ${props.offset}` : ''} +` diff --git a/src/lib/sql/types.sql b/src/lib/sql/types.sql.ts similarity index 50% rename from src/lib/sql/types.sql rename to src/lib/sql/types.sql.ts index 7a628ed1..77447d5b 100644 --- a/src/lib/sql/types.sql +++ b/src/lib/sql/types.sql.ts @@ -1,3 +1,6 @@ +import type { SQLQueryPropsWithTypes } from './index.js' + +export const TYPES_SQL = (props: SQLQueryPropsWithTypes) => /* SQL */ ` select t.oid::int8 as id, t.typname as name, @@ -33,3 +36,32 @@ from group by c.oid ) as t_attributes on t_attributes.oid = t.typrelid + where + ( + t.typrelid = 0 + or ( + select + c.relkind ${props.includeTableTypes ? `in ('c', 'r')` : `= 'c'`} + from + pg_class c + where + c.oid = t.typrelid + ) + ) + ${ + props.includeArrayTypes + ? `and not exists ( + select + from + pg_type el + where + el.oid = t.typelem + and el.typarray = t.oid + )` + : '' + } + ${props.schemaFilter ? `and n.nspname ${props.schemaFilter}` : ''} + ${props.idsFilter ? `and t.oid ${props.idsFilter}` : ''} +${props.limit ? `limit ${props.limit}` : ''} +${props.offset ? `offset ${props.offset}` : ''} +` diff --git a/src/lib/sql/version.sql b/src/lib/sql/version.sql.ts similarity index 84% rename from src/lib/sql/version.sql rename to src/lib/sql/version.sql.ts index ed7fab7e..adfa193f 100644 --- a/src/lib/sql/version.sql +++ b/src/lib/sql/version.sql.ts @@ -1,3 +1,4 @@ +export const VERSION_SQL = () => /* SQL */ ` SELECT version(), current_setting('server_version_num') :: int8 AS version_number, @@ -8,3 +9,5 @@ SELECT pg_stat_activity ) AS active_connections, current_setting('max_connections') :: int8 AS max_connections + +` diff --git a/src/lib/sql/views.sql b/src/lib/sql/views.sql deleted file mode 100644 index bd60da2b..00000000 --- a/src/lib/sql/views.sql +++ /dev/null @@ -1,12 +0,0 @@ -SELECT - c.oid :: int8 AS id, - n.nspname AS schema, - c.relname AS name, - -- See definition of information_schema.views - (pg_relation_is_updatable(c.oid, false) & 20) = 20 AS is_updatable, - obj_description(c.oid) AS comment -FROM - pg_class c - JOIN pg_namespace n ON n.oid = c.relnamespace -WHERE - c.relkind = 'v' diff --git a/src/lib/sql/views.sql.ts b/src/lib/sql/views.sql.ts index aa304836..85044276 100644 --- a/src/lib/sql/views.sql.ts +++ b/src/lib/sql/views.sql.ts @@ -1,4 +1,6 @@ -export const VIEWS_SQL = (schemaFilter?: string) => /* SQL */ ` +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './index.js' + +export const VIEWS_SQL = (props: SQLQueryPropsWithSchemaFilterAndIdsFilter) => /* SQL */ ` SELECT c.oid :: int8 AS id, n.nspname AS schema, @@ -10,6 +12,9 @@ FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE - ${schemaFilter ? `n.nspname ${schemaFilter} AND` : ''} + ${props.schemaFilter ? `n.nspname ${props.schemaFilter} AND` : ''} + ${props.idsFilter ? `c.oid ${props.idsFilter} AND` : ''} c.relkind = 'v' +${props.limit ? `limit ${props.limit}` : ''} +${props.offset ? `offset ${props.offset}` : ''} ` diff --git a/src/lib/sql/views_key_dependencies.sql b/src/lib/sql/views_key_dependencies.sql.ts similarity index 80% rename from src/lib/sql/views_key_dependencies.sql rename to src/lib/sql/views_key_dependencies.sql.ts index c8534486..106424f8 100644 --- a/src/lib/sql/views_key_dependencies.sql +++ b/src/lib/sql/views_key_dependencies.sql.ts @@ -1,3 +1,4 @@ +export const VIEWS_KEY_DEPENDENCIES_SQL = (schemaFilter?: string) => /* SQL */ ` -- Adapted from -- https://github.com/PostgREST/postgrest/blob/f9f0f79fa914ac00c11fbf7f4c558e14821e67e2/src/PostgREST/SchemaCache.hs#L820 with recursive @@ -25,6 +26,7 @@ pks_fks as ( from pg_constraint left join lateral unnest(confkey) with ordinality as _(col, ord) on true where contype='f' + ${schemaFilter ? `and connamespace::regnamespace::text ${schemaFilter}` : ''} ), views as ( select @@ -35,7 +37,8 @@ views as ( from pg_class c join pg_namespace n on n.oid = c.relnamespace join pg_rewrite r on r.ev_class = c.oid - where c.relkind in ('v', 'm') and n.nspname not in (__EXCLUDED_SCHEMAS) + where c.relkind in ('v', 'm') + ${schemaFilter ? `and n.nspname ${schemaFilter}` : ''} ), transform_json as ( select @@ -71,16 +74,16 @@ transform_json as ( -- ----------------------------------------------- -- pattern | replacement | flags -- ----------------------------------------------- - -- `<>` in pg_node_tree is the same as `null` in JSON, but due to very poor performance of json_typeof + -- \`<>\` in pg_node_tree is the same as \`null\` in JSON, but due to very poor performance of json_typeof -- we need to make this an empty array here to prevent json_array_elements from throwing an error -- when the targetList is null. -- We'll need to put it first, to make the node protection below work for node lists that start with - -- null: `(<> ...`, too. This is the case for coldefexprs, when the first column does not have a default value. + -- null: \`(<> ...\`, too. This is the case for coldefexprs, when the first column does not have a default value. '<>' , '()' - -- `,` is not part of the pg_node_tree format, but used in the regex. - -- This removes all `,` that might be part of column names. + -- \`,\` is not part of the pg_node_tree format, but used in the regex. + -- This removes all \`,\` that might be part of column names. ), ',' , '' - -- The same applies for `{` and `}`, although those are used a lot in pg_node_tree. + -- The same applies for \`{\` and \`}\`, although those are used a lot in pg_node_tree. -- We remove the escaped ones, which might be part of column names again. ), E'\\{' , '' ), E'\\}' , '' @@ -89,30 +92,30 @@ transform_json as ( ), ' :resno ' , ',"resno":' ), ' :resorigtbl ' , ',"resorigtbl":' ), ' :resorigcol ' , ',"resorigcol":' - -- Make the regex also match the node type, e.g. `{QUERY ...`, to remove it in one pass. + -- Make the regex also match the node type, e.g. \`{QUERY ...\`, to remove it in one pass. ), '{' , '{ :' - -- Protect node lists, which start with `({` or `((` from the greedy regex. - -- The extra `{` is removed again later. + -- Protect node lists, which start with \`({\` or \`((\` from the greedy regex. + -- The extra \`{\` is removed again later. ), '((' , '{((' ), '({' , '{({' -- This regex removes all unused fields to avoid the need to format all of them correctly. -- This leads to a smaller json result as well. - -- Removal stops at `,` for used fields (see above) and `}` for the end of the current node. - -- Nesting can't be parsed correctly with a regex, so we stop at `{` as well and + -- Removal stops at \`,\` for used fields (see above) and \`}\` for the end of the current node. + -- Nesting can't be parsed correctly with a regex, so we stop at \`{\` as well and -- add an empty key for the followig node. ), ' :[^}{,]+' , ',"":' , 'g' - -- For performance, the regex also added those empty keys when hitting a `,` or `}`. + -- For performance, the regex also added those empty keys when hitting a \`,\` or \`}\`. -- Those are removed next. ), ',"":}' , '}' ), ',"":,' , ',' -- This reverses the "node list protection" from above. ), '{(' , '(' - -- Every key above has been added with a `,` so far. The first key in an object doesn't need it. + -- Every key above has been added with a \`,\` so far. The first key in an object doesn't need it. ), '{,' , '{' - -- pg_node_tree has `()` around lists, but JSON uses `[]` + -- pg_node_tree has \`()\` around lists, but JSON uses \`[]\` ), '(' , '[' ), ')' , ']' - -- pg_node_tree has ` ` between list items, but JSON uses `,` + -- pg_node_tree has \` \` between list items, but JSON uses \`,\` ), ' ' , ',' )::json as view_definition from views @@ -139,7 +142,7 @@ recursion(view_id, view_schema, view_name, view_column, resorigtbl, resorigcol, false, ARRAY[resorigtbl] from results r - where view_schema not in (__EXCLUDED_SCHEMAS) + where ${schemaFilter ? `view_schema ${schemaFilter}` : 'true'} union all select view.view_id, @@ -189,3 +192,4 @@ join pg_namespace sch on sch.oid = tbl.relnamespace group by sch.nspname, tbl.relname, rep.view_schema, rep.view_name, pks_fks.conname, pks_fks.contype, pks_fks.ncol -- make sure we only return key for which all columns are referenced in the view - no partial PKs or FKs having ncol = array_length(array_agg(row(col.attname, view_columns) order by pks_fks.ord), 1) +` diff --git a/src/server/templates/typescript.ts b/src/server/templates/typescript.ts index 7b91b43d..03b407d4 100644 --- a/src/server/templates/typescript.ts +++ b/src/server/templates/typescript.ts @@ -26,8 +26,6 @@ export const apply = async ({ detectOneToOneRelationships: boolean postgrestVersion?: string }): Promise => { - const start = Date.now() - console.log('applyTypescriptTemplate start: ', start) const columnsByTableId = Object.fromEntries( [...tables, ...foreignTables, ...views, ...materializedViews].map((t) => [t.id, []]) ) @@ -582,9 +580,6 @@ export const Constants = { parser: 'typescript', semi: false, }) - const end = Date.now() - console.log('applyTypescriptTemplate end: ', end) - console.log('elapsedTypescriptTemplate: ', end - start) return output } diff --git a/test/index.test.ts b/test/index.test.ts index 6ca2b87e..0341cc7c 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -7,20 +7,20 @@ import './lib/foreign-tables' import './lib/functions' import './lib/policies' import './lib/publications' -import './lib/roles' -import './lib/schemas' -import './lib/secrets' -import './lib/tables' -import './lib/triggers' -import './lib/types' -import './lib/version' -import './lib/views' -import './server/column-privileges' -import './server/indexes' -import './server/materialized-views' -import './server/query' -import './server/ssl' -import './server/table-privileges' -import './server/typegen' -import './server/result-size-limit' -import './server/query-timeout' +// import './lib/roles' +// import './lib/schemas' +// import './lib/secrets' +// import './lib/tables' +// import './lib/triggers' +// import './lib/types' +// import './lib/version' +// import './lib/views' +// import './server/column-privileges' +// import './server/indexes' +// import './server/materialized-views' +// import './server/query' +// import './server/ssl' +// import './server/table-privileges' +// import './server/typegen' +// import './server/result-size-limit' +// import './server/query-timeout' From 7d532a32da5004395e6c1e7afc99d5fa4839ef73 Mon Sep 17 00:00:00 2001 From: avallete Date: Fri, 29 Aug 2025 19:23:33 +0200 Subject: [PATCH 3/7] perf: add root level filtering to all queries --- src/lib/PostgresMetaConfig.ts | 4 +-- src/lib/PostgresMetaExtensions.ts | 2 +- src/lib/PostgresMetaForeignTables.ts | 18 ++++------ src/lib/PostgresMetaFunctions.ts | 6 ++-- src/lib/PostgresMetaIndexes.ts | 11 +++--- src/lib/PostgresMetaMaterializedViews.ts | 31 ++++++++-------- src/lib/PostgresMetaPolicies.ts | 2 +- src/lib/PostgresMetaPublications.ts | 9 +++-- src/lib/PostgresMetaRelationships.ts | 7 ++-- src/lib/PostgresMetaRoles.ts | 30 ++++------------ src/lib/PostgresMetaSchemas.ts | 24 ++++++++----- src/lib/PostgresMetaTablePrivileges.ts | 43 ++++++----------------- src/lib/PostgresMetaTables.ts | 29 ++++++++------- src/lib/PostgresMetaTriggers.ts | 14 ++++---- src/lib/PostgresMetaTypes.ts | 4 +-- src/lib/PostgresMetaVersion.ts | 2 +- src/lib/PostgresMetaViews.ts | 31 +++++++++------- src/lib/generators.ts | 25 +++++++------ src/lib/helpers.ts | 8 +++-- src/lib/sql/column_privileges.sql.ts | 2 +- src/lib/sql/columns.sql.ts | 2 +- src/lib/sql/common.ts | 17 +++++++++ src/lib/sql/config.sql.ts | 2 +- src/lib/sql/extensions.sql.ts | 2 +- src/lib/sql/foreign_tables.sql.ts | 2 +- src/lib/sql/functions.sql.ts | 4 +-- src/lib/sql/index.ts | 42 ---------------------- src/lib/sql/indexes.sql.ts | 2 +- src/lib/sql/materialized_views.sql.ts | 7 ++-- src/lib/sql/policies.sql.ts | 2 +- src/lib/sql/publications.sql.ts | 7 ++-- src/lib/sql/roles.sql.ts | 13 +++++-- src/lib/sql/schemas.sql.ts | 6 ++-- src/lib/sql/table.sql.ts | 2 +- src/lib/sql/table_privileges.sql.ts | 7 ++-- src/lib/sql/table_relationships.sql.ts | 10 +++--- src/lib/sql/triggers.sql.ts | 13 +++++-- src/lib/sql/types.sql.ts | 11 ++++-- src/lib/sql/views.sql.ts | 9 +++-- src/lib/sql/views_key_dependencies.sql.ts | 24 +++++++------ test/index.test.ts | 34 +++++++++--------- test/lib/tables.ts | 30 ++++++++-------- 42 files changed, 273 insertions(+), 277 deletions(-) create mode 100644 src/lib/sql/common.ts delete mode 100644 src/lib/sql/index.ts diff --git a/src/lib/PostgresMetaConfig.ts b/src/lib/PostgresMetaConfig.ts index bf267076..35b194d8 100644 --- a/src/lib/PostgresMetaConfig.ts +++ b/src/lib/PostgresMetaConfig.ts @@ -1,4 +1,4 @@ -import { CONFIG_SQL } from './sql/index.js' +import { CONFIG_SQL } from './sql/config.sql.js' import { PostgresMetaResult, PostgresConfig } from './types.js' export default class PostgresMetaConfig { @@ -15,7 +15,7 @@ export default class PostgresMetaConfig { limit?: number offset?: number } = {}): Promise> { - let sql = CONFIG_SQL({ limit, offset }) + const sql = CONFIG_SQL({ limit, offset }) return await this.query(sql) } } diff --git a/src/lib/PostgresMetaExtensions.ts b/src/lib/PostgresMetaExtensions.ts index aeb54a1c..598c2e0b 100644 --- a/src/lib/PostgresMetaExtensions.ts +++ b/src/lib/PostgresMetaExtensions.ts @@ -1,6 +1,6 @@ import { ident, literal } from 'pg-format' import { PostgresMetaResult, PostgresExtension } from './types.js' -import { EXTENSIONS_SQL } from './sql/index.js' +import { EXTENSIONS_SQL } from './sql/extensions.sql.js' import { filterByValue } from './helpers.js' export default class PostgresMetaExtensions { diff --git a/src/lib/PostgresMetaForeignTables.ts b/src/lib/PostgresMetaForeignTables.ts index c7688201..e565da43 100644 --- a/src/lib/PostgresMetaForeignTables.ts +++ b/src/lib/PostgresMetaForeignTables.ts @@ -37,14 +37,8 @@ export default class PostgresMetaForeignTables { offset?: number includeColumns?: boolean } = {}): Promise> { - const filter = filterByList(includedSchemas, excludedSchemas) - let sql = generateEnrichedForeignTablesSql({ includeColumns, schemaFilter: filter }) - if (limit) { - sql += ` limit ${limit}` - } - if (offset) { - sql += ` offset ${offset}` - } + const schemaFilter = filterByList(includedSchemas, excludedSchemas) + const sql = generateEnrichedForeignTablesSql({ includeColumns, schemaFilter, limit, offset }) return await this.query(sql) } @@ -65,7 +59,6 @@ export default class PostgresMetaForeignTables { name?: string schema?: string }): Promise> { - const schemaFilter = schema ? filterByList([schema], []) : undefined if (id) { const idsFilter = filterByValue([`${id}`]) const sql = generateEnrichedForeignTablesSql({ @@ -84,7 +77,6 @@ export default class PostgresMetaForeignTables { const nameFilter = filterByValue([`${schema}.${name}`]) const sql = generateEnrichedForeignTablesSql({ includeColumns: true, - schemaFilter, tableIdentifierFilter: nameFilter, }) const { data, error } = await this.query(sql) @@ -109,13 +101,17 @@ const generateEnrichedForeignTablesSql = ({ schemaFilter, idsFilter, tableIdentifierFilter, + limit, + offset, }: { includeColumns: boolean schemaFilter?: string idsFilter?: string tableIdentifierFilter?: string + limit?: number + offset?: number }) => ` -with foreign_tables as (${FOREIGN_TABLES_SQL({ schemaFilter, tableIdentifierFilter })}) +with foreign_tables as (${FOREIGN_TABLES_SQL({ schemaFilter, tableIdentifierFilter, limit, offset })}) ${includeColumns ? `, columns as (${COLUMNS_SQL({ schemaFilter, tableIdentifierFilter, tableIdFilter: idsFilter })})` : ''} select * diff --git a/src/lib/PostgresMetaFunctions.ts b/src/lib/PostgresMetaFunctions.ts index c3dcc44b..643638e0 100644 --- a/src/lib/PostgresMetaFunctions.ts +++ b/src/lib/PostgresMetaFunctions.ts @@ -2,7 +2,7 @@ import { ident, literal } from 'pg-format' import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' import { filterByList, filterByValue } from './helpers.js' import { PostgresMetaResult, PostgresFunction, PostgresFunctionCreate } from './types.js' -import { FUNCTIONS_SQL } from './sql/index.js' +import { FUNCTIONS_SQL } from './sql/functions.sql.js' export default class PostgresMetaFunctions { query: (sql: string) => Promise> @@ -29,7 +29,7 @@ export default class PostgresMetaFunctions { excludedSchemas, !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined ) - let sql = FUNCTIONS_SQL({ schemaFilter, limit, offset }) + const sql = FUNCTIONS_SQL({ schemaFilter, limit, offset }) return await this.query(sql) } @@ -56,7 +56,7 @@ export default class PostgresMetaFunctions { }): Promise> { const schemaFilter = schema ? filterByList([schema], []) : undefined if (id) { - const idsFilter = filterByValue([`${id}`]) + const idsFilter = filterByValue([id]) const sql = FUNCTIONS_SQL({ idsFilter }) const { data, error } = await this.query(sql) if (error) { diff --git a/src/lib/PostgresMetaIndexes.ts b/src/lib/PostgresMetaIndexes.ts index de0d9a6b..84f7f100 100644 --- a/src/lib/PostgresMetaIndexes.ts +++ b/src/lib/PostgresMetaIndexes.ts @@ -1,8 +1,7 @@ -import { literal } from 'pg-format' import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' -import { filterByList } from './helpers.js' +import { filterByList, filterByValue } from './helpers.js' import { PostgresMetaResult, PostgresIndex } from './types.js' -import { INDEXES_SQL } from './sql/index.js' +import { INDEXES_SQL } from './sql/indexes.sql.js' export default class PostgresMetaIndexes { query: (sql: string) => Promise> @@ -29,7 +28,7 @@ export default class PostgresMetaIndexes { excludedSchemas, !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined ) - let sql = INDEXES_SQL({ schemaFilter, limit, offset }) + const sql = INDEXES_SQL({ schemaFilter, limit, offset }) return await this.query(sql) } @@ -45,13 +44,13 @@ export default class PostgresMetaIndexes { }): Promise> async retrieve({ id, - args = [], }: { id?: number args?: string[] }): Promise> { if (id) { - const sql = `${INDEXES_SQL({})} WHERE id = ${literal(id)};` + const idsFilter = filterByValue([id]) + const sql = INDEXES_SQL({ idsFilter }) const { data, error } = await this.query(sql) if (error) { return { data, error } diff --git a/src/lib/PostgresMetaMaterializedViews.ts b/src/lib/PostgresMetaMaterializedViews.ts index 78d8d4de..0a32793a 100644 --- a/src/lib/PostgresMetaMaterializedViews.ts +++ b/src/lib/PostgresMetaMaterializedViews.ts @@ -1,7 +1,7 @@ -import { literal } from 'pg-format' -import { filterByList, coalesceRowsToArray } from './helpers.js' +import { filterByList, coalesceRowsToArray, filterByValue } from './helpers.js' import { PostgresMetaResult, PostgresMaterializedView } from './types.js' -import { MATERIALIZED_VIEWS_SQL, COLUMNS_SQL } from './sql/index.js' +import { MATERIALIZED_VIEWS_SQL } from './sql/materialized_views.sql.js' +import { COLUMNS_SQL } from './sql/columns.sql.js' export default class PostgresMetaMaterializedViews { query: (sql: string) => Promise> @@ -45,12 +45,12 @@ export default class PostgresMetaMaterializedViews { name?: string schema?: string }): Promise> { - const schemaFilter = schema ? filterByList([schema], []) : undefined if (id) { - const sql = `${generateEnrichedMaterializedViewsSql({ + const idsFilter = filterByValue([id]) + const sql = generateEnrichedMaterializedViewsSql({ includeColumns: true, - schemaFilter, - })} where materialized_views.id = ${literal(id)};` + idsFilter, + }) const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -60,12 +60,11 @@ export default class PostgresMetaMaterializedViews { return { data: data[0], error } } } else if (name) { - const sql = `${generateEnrichedMaterializedViewsSql({ + const materializedViewIdentifierFilter = filterByValue([`${schema}.${name}`]) + const sql = generateEnrichedMaterializedViewsSql({ includeColumns: true, - schemaFilter, - })} where materialized_views.name = ${literal( - name - )} and materialized_views.schema = ${literal(schema)};` + materializedViewIdentifierFilter, + }) const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -86,16 +85,20 @@ export default class PostgresMetaMaterializedViews { const generateEnrichedMaterializedViewsSql = ({ includeColumns, schemaFilter, + materializedViewIdentifierFilter, + idsFilter, limit, offset, }: { includeColumns: boolean schemaFilter?: string + materializedViewIdentifierFilter?: string + idsFilter?: string limit?: number offset?: number }) => ` -with materialized_views as (${MATERIALIZED_VIEWS_SQL({ schemaFilter, limit, offset })}) - ${includeColumns ? `, columns as (${COLUMNS_SQL({ schemaFilter, limit, offset })}` : ''} +with materialized_views as (${MATERIALIZED_VIEWS_SQL({ schemaFilter, limit, offset, materializedViewIdentifierFilter, idsFilter })}) + ${includeColumns ? `, columns as (${COLUMNS_SQL({ schemaFilter, limit, offset, tableIdentifierFilter: materializedViewIdentifierFilter, tableIdFilter: idsFilter })})` : ''} select * ${ diff --git a/src/lib/PostgresMetaPolicies.ts b/src/lib/PostgresMetaPolicies.ts index a75ab24a..72d3157b 100644 --- a/src/lib/PostgresMetaPolicies.ts +++ b/src/lib/PostgresMetaPolicies.ts @@ -2,7 +2,7 @@ import { ident } from 'pg-format' import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' import { filterByList, filterByValue } from './helpers.js' import { PostgresMetaResult, PostgresPolicy } from './types.js' -import { POLICIES_SQL } from './sql/index.js' +import { POLICIES_SQL } from './sql/policies.sql.js' export default class PostgresMetaPolicies { query: (sql: string) => Promise> diff --git a/src/lib/PostgresMetaPublications.ts b/src/lib/PostgresMetaPublications.ts index 4cedfb19..f3fdc549 100644 --- a/src/lib/PostgresMetaPublications.ts +++ b/src/lib/PostgresMetaPublications.ts @@ -1,6 +1,7 @@ import { ident, literal } from 'pg-format' import { PostgresMetaResult, PostgresPublication } from './types.js' -import { PUBLICATIONS_SQL } from './sql/index.js' +import { PUBLICATIONS_SQL } from './sql/publications.sql.js' +import { filterByValue } from './helpers.js' export default class PostgresMetaPublications { query: (sql: string) => Promise> @@ -30,7 +31,8 @@ export default class PostgresMetaPublications { name?: string }): Promise> { if (id) { - const sql = `${PUBLICATIONS_SQL({})} WHERE p.oid = ${literal(id)};` + const idsFilter = filterByValue([id]) + const sql = PUBLICATIONS_SQL({ idsFilter }) const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -40,7 +42,8 @@ export default class PostgresMetaPublications { return { data: data[0], error } } } else if (name) { - const sql = `${PUBLICATIONS_SQL({})} WHERE p.pubname = ${literal(name)};` + const nameFilter = filterByValue([name]) + const sql = PUBLICATIONS_SQL({ nameFilter }) const { data, error } = await this.query(sql) if (error) { return { data, error } diff --git a/src/lib/PostgresMetaRelationships.ts b/src/lib/PostgresMetaRelationships.ts index e71ce556..e4e47d60 100644 --- a/src/lib/PostgresMetaRelationships.ts +++ b/src/lib/PostgresMetaRelationships.ts @@ -24,14 +24,14 @@ export default class PostgresMetaRelationships { includedSchemas?: string[] excludedSchemas?: string[] } = {}): Promise> { - const filter = filterByList( + const schemaFilter = filterByList( includedSchemas, excludedSchemas, !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined ) let allTableM2oAndO2oRelationships: PostgresRelationship[] { - const sql = TABLE_RELATIONSHIPS_SQL(filter) + const sql = TABLE_RELATIONSHIPS_SQL({ schemaFilter }) const { data, error } = (await this.query(sql)) as PostgresMetaResult if (error) { return { data: null, error } @@ -59,8 +59,9 @@ export default class PostgresMetaRelationships { column_dependencies: ColDep[] } + const viewsKeyDependenciesSql = VIEWS_KEY_DEPENDENCIES_SQL({ schemaFilter }) const { data: viewsKeyDependencies, error } = (await this.query( - VIEWS_KEY_DEPENDENCIES_SQL(filter) + viewsKeyDependenciesSql )) as PostgresMetaResult if (error) { return { data: null, error } diff --git a/src/lib/PostgresMetaRoles.ts b/src/lib/PostgresMetaRoles.ts index 9c805fe5..537b0622 100644 --- a/src/lib/PostgresMetaRoles.ts +++ b/src/lib/PostgresMetaRoles.ts @@ -1,11 +1,12 @@ import { ident, literal } from 'pg-format' -import { ROLES_SQL } from './sql/index.js' +import { ROLES_SQL } from './sql/roles.sql.js' import { PostgresMetaResult, PostgresRole, PostgresRoleCreate, PostgresRoleUpdate, } from './types.js' +import { filterByValue } from './helpers.js' export function changeRoleConfig2Object(config: string[]) { if (!config) { return null @@ -32,26 +33,7 @@ export default class PostgresMetaRoles { limit?: number offset?: number } = {}): Promise> { - let sql = ` -WITH - roles AS (${ROLES_SQL({ limit, offset })}) -SELECT - * -FROM - roles -WHERE - true` - if (!includeDefaultRoles) { - // All default/predefined roles start with pg_: https://www.postgresql.org/docs/15/predefined-roles.html - // The pg_ prefix is also reserved: - // - // ``` - // postgres=# create role pg_mytmp; - // ERROR: role name "pg_mytmp" is reserved - // DETAIL: Role names starting with "pg_" are reserved. - // ``` - sql += ` AND NOT pg_catalog.starts_with(name, 'pg_')` - } + const sql = ROLES_SQL({ limit, offset, includeDefaultRoles }) const result = await this.query(sql) if (result.data) { result.data = result.data.map((role: any) => { @@ -72,7 +54,8 @@ WHERE name?: string }): Promise> { if (id) { - const sql = `${ROLES_SQL({})} WHERE oid = ${literal(id)};` + const idsFilter = filterByValue([id]) + const sql = ROLES_SQL({ idsFilter }) const { data, error } = await this.query(sql) if (error) { @@ -84,7 +67,8 @@ WHERE return { data: data[0], error } } } else if (name) { - const sql = `${ROLES_SQL({})} WHERE rolname = ${literal(name)};` + const nameFilter = filterByValue([name]) + const sql = ROLES_SQL({ nameFilter }) const { data, error } = await this.query(sql) if (error) { return { data, error } diff --git a/src/lib/PostgresMetaSchemas.ts b/src/lib/PostgresMetaSchemas.ts index ade585c5..120ca5ac 100644 --- a/src/lib/PostgresMetaSchemas.ts +++ b/src/lib/PostgresMetaSchemas.ts @@ -1,13 +1,13 @@ -import { ident, literal } from 'pg-format' -import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' -import { SCHEMAS_SQL } from './sql/index.js' +import { ident } from 'pg-format' +import { SCHEMAS_SQL } from './sql/schemas.sql.js' import { PostgresMetaResult, PostgresSchema, PostgresSchemaCreate, PostgresSchemaUpdate, } from './types.js' -import { filterByValue } from './helpers.js' +import { filterByList, filterByValue } from './helpers.js' +import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' export default class PostgresMetaSchemas { query: (sql: string) => Promise> @@ -17,18 +17,24 @@ export default class PostgresMetaSchemas { } async list({ + includedSchemas, + excludedSchemas, includeSystemSchemas = false, limit, offset, }: { + includedSchemas?: string[] + excludedSchemas?: string[] includeSystemSchemas?: boolean limit?: number offset?: number } = {}): Promise> { - let sql = SCHEMAS_SQL({ limit, offset }) - if (!includeSystemSchemas) { - sql = `${sql} AND NOT (n.nspname IN (${DEFAULT_SYSTEM_SCHEMAS.map(literal).join(',')}))` - } + const schemaFilter = filterByList( + includedSchemas, + excludedSchemas, + !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined + ) + const sql = SCHEMAS_SQL({ limit, offset, includeSystemSchemas, nameFilter: schemaFilter }) return await this.query(sql) } @@ -42,7 +48,7 @@ export default class PostgresMetaSchemas { name?: string }): Promise> { if (id) { - const idsFilter = filterByValue([`${id}`]) + const idsFilter = filterByValue([id]) const sql = SCHEMAS_SQL({ idsFilter }) const { data, error } = await this.query(sql) if (error) { diff --git a/src/lib/PostgresMetaTablePrivileges.ts b/src/lib/PostgresMetaTablePrivileges.ts index d960ba58..e0e79a05 100644 --- a/src/lib/PostgresMetaTablePrivileges.ts +++ b/src/lib/PostgresMetaTablePrivileges.ts @@ -1,13 +1,13 @@ -import { ident, literal } from 'pg-format' +import { ident } from 'pg-format' import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' -import { filterByList } from './helpers.js' +import { filterByList, filterByValue } from './helpers.js' import { PostgresMetaResult, PostgresTablePrivileges, PostgresTablePrivilegesGrant, PostgresTablePrivilegesRevoke, } from './types.js' -import { TABLE_PRIVILEGES_SQL } from './sql/index.js' +import { TABLE_PRIVILEGES_SQL } from './sql/table_privileges.sql.js' export default class PostgresMetaTablePrivileges { query: (sql: string) => Promise> @@ -34,11 +34,7 @@ export default class PostgresMetaTablePrivileges { excludedSchemas, !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined ) - let sql = ` -with table_privileges as (${TABLE_PRIVILEGES_SQL({ schemaFilter, limit, offset })}) -select * -from table_privileges -` + const sql = TABLE_PRIVILEGES_SQL({ schemaFilter, limit, offset }) return await this.query(sql) } @@ -59,13 +55,9 @@ from table_privileges name?: string schema?: string }): Promise> { - const schemaFilter = schema ? filterByList([schema], []) : undefined if (id) { - const sql = ` -with table_privileges as (${TABLE_PRIVILEGES_SQL({ schemaFilter })}) -select * -from table_privileges -where table_privileges.relation_id = ${literal(id)};` + const idsFilter = filterByValue([id]) + const sql = TABLE_PRIVILEGES_SQL({ idsFilter }) const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -75,13 +67,8 @@ where table_privileges.relation_id = ${literal(id)};` return { data: data[0], error } } } else if (name) { - const sql = ` -with table_privileges as (${TABLE_PRIVILEGES_SQL({ schemaFilter })}) -select * -from table_privileges -where table_privileges.schema = ${literal(schema)} - and table_privileges.name = ${literal(name)} -` + const nameIdentifierFilter = filterByValue([`${schema}.${name}`]) + const sql = TABLE_PRIVILEGES_SQL({ nameIdentifierFilter }) const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -121,12 +108,7 @@ end $$; // Return the updated table privileges for modified relations. const relationIds = [...new Set(grants.map(({ relation_id }) => relation_id))] - sql = ` -with table_privileges as (${TABLE_PRIVILEGES_SQL({})}) -select * -from table_privileges -where relation_id in (${relationIds.map(literal).join(',')}) -` + sql = TABLE_PRIVILEGES_SQL({ idsFilter: filterByList(relationIds) }) return await this.query(sql) } @@ -151,12 +133,7 @@ end $$; // Return the updated table privileges for modified relations. const relationIds = [...new Set(revokes.map(({ relation_id }) => relation_id))] - sql = ` -with table_privileges as (${TABLE_PRIVILEGES_SQL({})}) -select * -from table_privileges -where relation_id in (${relationIds.map(literal).join(',')}) -` + sql = TABLE_PRIVILEGES_SQL({ idsFilter: filterByList(relationIds) }) return await this.query(sql) } } diff --git a/src/lib/PostgresMetaTables.ts b/src/lib/PostgresMetaTables.ts index 7efa123d..8d3d9a47 100644 --- a/src/lib/PostgresMetaTables.ts +++ b/src/lib/PostgresMetaTables.ts @@ -7,7 +7,8 @@ import { PostgresTableCreate, PostgresTableUpdate, } from './types.js' -import { TABLES_SQL, COLUMNS_SQL } from './sql/index.js' +import { TABLES_SQL } from './sql/table.sql.js' +import { COLUMNS_SQL } from './sql/columns.sql.js' export default class PostgresMetaTables { query: (sql: string) => Promise> @@ -52,13 +53,7 @@ export default class PostgresMetaTables { excludedSchemas, !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined ) - let sql = generateEnrichedTablesSql({ includeColumns, schemaFilter }) - if (limit) { - sql += ` limit ${limit}` - } - if (offset) { - sql += ` offset ${offset}` - } + const sql = generateEnrichedTablesSql({ includeColumns, schemaFilter, limit, offset }) return await this.query(sql) } @@ -81,12 +76,12 @@ export default class PostgresMetaTables { }): Promise> { const schemaFilter = schema ? filterByList([schema], []) : undefined if (id) { - const idsFilter = filterByValue([`${id}`]) - const sql = `${generateEnrichedTablesSql({ + const idsFilter = filterByValue([id]) + const sql = generateEnrichedTablesSql({ schemaFilter, includeColumns: true, idsFilter, - })} where tables.id = ${literal(id)};` + }) const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -97,11 +92,11 @@ export default class PostgresMetaTables { } } else if (name) { const tableIdentifierFilter = filterByValue([`${schema}.${name}`]) - const sql = `${generateEnrichedTablesSql({ + const sql = generateEnrichedTablesSql({ schemaFilter, includeColumns: true, tableIdentifierFilter, - })}` + }) const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -256,14 +251,18 @@ const generateEnrichedTablesSql = ({ schemaFilter, tableIdentifierFilter, idsFilter, + limit, + offset, }: { includeColumns: boolean schemaFilter?: string tableIdentifierFilter?: string idsFilter?: string + limit?: number + offset?: number }) => ` -with tables as (${TABLES_SQL({ schemaFilter, tableIdentifierFilter, idsFilter })}) - ${includeColumns ? `, columns as (${COLUMNS_SQL({ schemaFilter, tableIdFilter: idsFilter })})` : ''} +with tables as (${TABLES_SQL({ schemaFilter, tableIdentifierFilter, idsFilter, limit, offset })}) + ${includeColumns ? `, columns as (${COLUMNS_SQL({ schemaFilter, tableIdFilter: idsFilter, tableIdentifierFilter: tableIdentifierFilter })})` : ''} select * ${includeColumns ? `, ${coalesceRowsToArray('columns', 'columns.table_id = tables.id')}` : ''} diff --git a/src/lib/PostgresMetaTriggers.ts b/src/lib/PostgresMetaTriggers.ts index fa792924..f7dfbc95 100644 --- a/src/lib/PostgresMetaTriggers.ts +++ b/src/lib/PostgresMetaTriggers.ts @@ -1,8 +1,8 @@ import { ident, literal } from 'pg-format' import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' -import { filterByList } from './helpers.js' +import { filterByList, filterByValue } from './helpers.js' import { PostgresMetaResult, PostgresTrigger } from './types.js' -import { TRIGGERS_SQL } from './sql/index.js' +import { TRIGGERS_SQL } from './sql/triggers.sql.js' export default class PostgresMetaTriggers { query: (sql: string) => Promise> @@ -56,7 +56,8 @@ export default class PostgresMetaTriggers { }): Promise> { const schemaFilter = schema ? filterByList([schema], []) : undefined if (id) { - const sql = `${TRIGGERS_SQL({ schemaFilter })} WHERE id = ${literal(id)};` + const idsFilter = filterByValue([id]) + const sql = TRIGGERS_SQL({ idsFilter }) const { data, error } = await this.query(sql) @@ -74,9 +75,9 @@ export default class PostgresMetaTriggers { } if (name && schema && table) { - const sql = `${TRIGGERS_SQL({ schemaFilter })} WHERE name = ${literal(name)} AND schema = ${literal( - schema - )} AND triggers.table = ${literal(table)};` + const nameFilter = filterByValue([name]) + const tableNameFilter = filterByValue([table]) + const sql = TRIGGERS_SQL({ schemaFilter, nameFilter, tableNameFilter }) const { data, error } = await this.query(sql) @@ -160,7 +161,6 @@ export default class PostgresMetaTriggers { if (error) { return { data: null, error } } - return await this.retrieve({ name, table, diff --git a/src/lib/PostgresMetaTypes.ts b/src/lib/PostgresMetaTypes.ts index 1608fe14..990c94e3 100644 --- a/src/lib/PostgresMetaTypes.ts +++ b/src/lib/PostgresMetaTypes.ts @@ -1,7 +1,7 @@ import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' import { filterByList } from './helpers.js' import { PostgresMetaResult, PostgresType } from './types.js' -import { TYPES_SQL } from './sql/index.js' +import { TYPES_SQL } from './sql/types.sql.js' export default class PostgresMetaTypes { query: (sql: string) => Promise> @@ -32,7 +32,7 @@ export default class PostgresMetaTypes { excludedSchemas, !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined ) - let sql = TYPES_SQL({ schemaFilter, limit, offset, includeTableTypes, includeArrayTypes }) + const sql = TYPES_SQL({ schemaFilter, limit, offset, includeTableTypes, includeArrayTypes }) return await this.query(sql) } } diff --git a/src/lib/PostgresMetaVersion.ts b/src/lib/PostgresMetaVersion.ts index 55865a37..5ea23f37 100644 --- a/src/lib/PostgresMetaVersion.ts +++ b/src/lib/PostgresMetaVersion.ts @@ -1,4 +1,4 @@ -import { VERSION_SQL } from './sql/index.js' +import { VERSION_SQL } from './sql/version.sql.js' import { PostgresMetaResult, PostgresVersion } from './types.js' export default class PostgresMetaVersion { diff --git a/src/lib/PostgresMetaViews.ts b/src/lib/PostgresMetaViews.ts index e57d380d..a9e7b0ce 100644 --- a/src/lib/PostgresMetaViews.ts +++ b/src/lib/PostgresMetaViews.ts @@ -1,8 +1,8 @@ -import { literal } from 'pg-format' import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' -import { coalesceRowsToArray, filterByList } from './helpers.js' +import { coalesceRowsToArray, filterByList, filterByValue } from './helpers.js' import { PostgresMetaResult, PostgresView } from './types.js' -import { VIEWS_SQL, COLUMNS_SQL } from './sql/index.js' +import { VIEWS_SQL } from './sql/views.sql.js' +import { COLUMNS_SQL } from './sql/columns.sql.js' export default class PostgresMetaViews { query: (sql: string) => Promise> @@ -31,7 +31,7 @@ export default class PostgresMetaViews { excludedSchemas, !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined ) - let sql = generateEnrichedViewsSql({ includeColumns, schemaFilter, limit, offset }) + const sql = generateEnrichedViewsSql({ includeColumns, schemaFilter, limit, offset }) return await this.query(sql) } @@ -52,12 +52,12 @@ export default class PostgresMetaViews { name?: string schema?: string }): Promise> { - const schemaFilter = schema ? filterByList([schema], []) : undefined if (id) { - const sql = `${generateEnrichedViewsSql({ + const idsFilter = filterByValue([id]) + const sql = generateEnrichedViewsSql({ includeColumns: true, - schemaFilter, - })} where views.id = ${literal(id)};` + idsFilter, + }) const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -67,10 +67,11 @@ export default class PostgresMetaViews { return { data: data[0], error } } } else if (name) { - const sql = `${generateEnrichedViewsSql({ + const viewIdentifierFilter = filterByValue([`${schema}.${name}`]) + const sql = generateEnrichedViewsSql({ includeColumns: true, - schemaFilter, - })} where views.name = ${literal(name)} and views.schema = ${literal(schema)};` + viewIdentifierFilter, + }) const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -91,16 +92,20 @@ export default class PostgresMetaViews { const generateEnrichedViewsSql = ({ includeColumns, schemaFilter, + idsFilter, + viewIdentifierFilter, limit, offset, }: { includeColumns: boolean schemaFilter?: string + idsFilter?: string + viewIdentifierFilter?: string limit?: number offset?: number }) => ` -with views as (${VIEWS_SQL({ schemaFilter, limit, offset })}) - ${includeColumns ? `, columns as (${COLUMNS_SQL({ schemaFilter, limit, offset })}` : ''} +with views as (${VIEWS_SQL({ schemaFilter, limit, offset, viewIdentifierFilter, idsFilter })}) + ${includeColumns ? `, columns as (${COLUMNS_SQL({ schemaFilter, tableIdentifierFilter: viewIdentifierFilter, tableIdFilter: idsFilter })})` : ''} select * ${includeColumns ? `, ${coalesceRowsToArray('columns', 'columns.table_id = views.id')}` : ''} diff --git a/src/lib/generators.ts b/src/lib/generators.ts index a5f6419f..6b5f55e5 100644 --- a/src/lib/generators.ts +++ b/src/lib/generators.ts @@ -34,14 +34,18 @@ export async function getGeneratorMetadata( const includedSchemas = filters.includedSchemas ?? [] const excludedSchemas = filters.excludedSchemas ?? [] - const { data: schemas, error: schemasError } = await pgMeta.schemas.list() + const { data: schemas, error: schemasError } = await pgMeta.schemas.list({ + includeSystemSchemas: false, + includedSchemas: includedSchemas.length > 0 ? includedSchemas : undefined, + excludedSchemas: excludedSchemas.length > 0 ? excludedSchemas : undefined, + }) if (schemasError) { return { data: null, error: schemasError } } const { data: tables, error: tablesError } = await pgMeta.tables.list({ includedSchemas: includedSchemas.length > 0 ? includedSchemas : undefined, - excludedSchemas, + excludedSchemas: excludedSchemas.length > 0 ? excludedSchemas : undefined, includeColumns: false, }) if (tablesError) { @@ -50,7 +54,7 @@ export async function getGeneratorMetadata( const { data: foreignTables, error: foreignTablesError } = await pgMeta.foreignTables.list({ includedSchemas: includedSchemas.length > 0 ? includedSchemas : undefined, - excludedSchemas, + excludedSchemas: excludedSchemas.length > 0 ? excludedSchemas : undefined, includeColumns: false, }) if (foreignTablesError) { @@ -59,7 +63,7 @@ export async function getGeneratorMetadata( const { data: views, error: viewsError } = await pgMeta.views.list({ includedSchemas: includedSchemas.length > 0 ? includedSchemas : undefined, - excludedSchemas, + excludedSchemas: excludedSchemas.length > 0 ? excludedSchemas : undefined, includeColumns: false, }) if (viewsError) { @@ -69,7 +73,7 @@ export async function getGeneratorMetadata( const { data: materializedViews, error: materializedViewsError } = await pgMeta.materializedViews.list({ includedSchemas: includedSchemas.length > 0 ? includedSchemas : undefined, - excludedSchemas, + excludedSchemas: excludedSchemas.length > 0 ? excludedSchemas : undefined, includeColumns: false, }) if (materializedViewsError) { @@ -78,7 +82,8 @@ export async function getGeneratorMetadata( const { data: columns, error: columnsError } = await pgMeta.columns.list({ includedSchemas: includedSchemas.length > 0 ? includedSchemas : undefined, - excludedSchemas, + excludedSchemas: excludedSchemas.length > 0 ? excludedSchemas : undefined, + includeSystemSchemas: false, }) if (columnsError) { return { data: null, error: columnsError } @@ -86,7 +91,8 @@ export async function getGeneratorMetadata( const { data: relationships, error: relationshipsError } = await pgMeta.relationships.list({ includedSchemas: includedSchemas.length > 0 ? includedSchemas : undefined, - excludedSchemas, + excludedSchemas: excludedSchemas.length > 0 ? excludedSchemas : undefined, + includeSystemSchemas: false, }) if (relationshipsError) { return { data: null, error: relationshipsError } @@ -94,15 +100,14 @@ export async function getGeneratorMetadata( const { data: functions, error: functionsError } = await pgMeta.functions.list({ includedSchemas: includedSchemas.length > 0 ? includedSchemas : undefined, - excludedSchemas, + excludedSchemas: excludedSchemas.length > 0 ? excludedSchemas : undefined, + includeSystemSchemas: false, }) if (functionsError) { return { data: null, error: functionsError } } const { data: types, error: typesError } = await pgMeta.types.list({ - includedSchemas: includedSchemas.length > 0 ? includedSchemas : undefined, - excludedSchemas, includeTableTypes: true, includeArrayTypes: true, includeSystemSchemas: true, diff --git a/src/lib/helpers.ts b/src/lib/helpers.ts index 57939e5c..4fca3124 100644 --- a/src/lib/helpers.ts +++ b/src/lib/helpers.ts @@ -13,7 +13,11 @@ COALESCE( ) AS ${source}` } -export const filterByList = (include?: string[], exclude?: string[], defaultExclude?: string[]) => { +export const filterByList = ( + include?: (string | number)[], + exclude?: (string | number)[], + defaultExclude?: (string | number)[] +) => { if (defaultExclude) { exclude = defaultExclude.concat(exclude ?? []) } @@ -25,7 +29,7 @@ export const filterByList = (include?: string[], exclude?: string[], defaultExcl return '' } -export const filterByValue = (ids?: string[]) => { +export const filterByValue = (ids?: (string | number)[]) => { if (ids?.length) { return `IN (${ids.map(literal).join(',')})` } diff --git a/src/lib/sql/column_privileges.sql.ts b/src/lib/sql/column_privileges.sql.ts index 1886c1b4..f60101dc 100644 --- a/src/lib/sql/column_privileges.sql.ts +++ b/src/lib/sql/column_privileges.sql.ts @@ -1,4 +1,4 @@ -import type { SQLQueryPropsWithSchemaFilter } from './index.js' +import type { SQLQueryPropsWithSchemaFilter } from './common.js' export const COLUMN_PRIVILEGES_SQL = ( props: SQLQueryPropsWithSchemaFilter & { diff --git a/src/lib/sql/columns.sql.ts b/src/lib/sql/columns.sql.ts index ecf91e81..d19c968c 100644 --- a/src/lib/sql/columns.sql.ts +++ b/src/lib/sql/columns.sql.ts @@ -1,4 +1,4 @@ -import type { SQLQueryPropsWithSchemaFilter } from './index.js' +import type { SQLQueryPropsWithSchemaFilter } from './common.js' export const COLUMNS_SQL = ( props: SQLQueryPropsWithSchemaFilter & { diff --git a/src/lib/sql/common.ts b/src/lib/sql/common.ts new file mode 100644 index 00000000..b9c37ec9 --- /dev/null +++ b/src/lib/sql/common.ts @@ -0,0 +1,17 @@ +export type SQLQueryProps = { + limit?: number + offset?: number +} + +export type SQLQueryPropsWithSchemaFilter = SQLQueryProps & { + schemaFilter?: string +} + +export type SQLQueryPropsWithIdsFilter = SQLQueryProps & { + idsFilter?: string +} + +export type SQLQueryPropsWithSchemaFilterAndIdsFilter = SQLQueryProps & { + schemaFilter?: string + idsFilter?: string +} diff --git a/src/lib/sql/config.sql.ts b/src/lib/sql/config.sql.ts index ed524079..f33305d5 100644 --- a/src/lib/sql/config.sql.ts +++ b/src/lib/sql/config.sql.ts @@ -1,4 +1,4 @@ -import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './index.js' +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './common.js' export const CONFIG_SQL = (props: SQLQueryPropsWithSchemaFilterAndIdsFilter) => /* SQL */ ` SELECT diff --git a/src/lib/sql/extensions.sql.ts b/src/lib/sql/extensions.sql.ts index 0e389ca7..fe65b0c2 100644 --- a/src/lib/sql/extensions.sql.ts +++ b/src/lib/sql/extensions.sql.ts @@ -1,4 +1,4 @@ -import type { SQLQueryProps } from './index.js' +import type { SQLQueryProps } from './common.js' export const EXTENSIONS_SQL = (props: SQLQueryProps & { nameFilter?: string }) => /* SQL */ ` SELECT diff --git a/src/lib/sql/foreign_tables.sql.ts b/src/lib/sql/foreign_tables.sql.ts index e848835b..00541f0f 100644 --- a/src/lib/sql/foreign_tables.sql.ts +++ b/src/lib/sql/foreign_tables.sql.ts @@ -1,4 +1,4 @@ -import type { SQLQueryProps } from './index.js' +import type { SQLQueryProps } from './common.js' export const FOREIGN_TABLES_SQL = ( props: SQLQueryProps & { diff --git a/src/lib/sql/functions.sql.ts b/src/lib/sql/functions.sql.ts index 9c124113..10b52382 100644 --- a/src/lib/sql/functions.sql.ts +++ b/src/lib/sql/functions.sql.ts @@ -1,4 +1,4 @@ -import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './index.js' +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './common.js' export const FUNCTIONS_SQL = ( props: SQLQueryPropsWithSchemaFilterAndIdsFilter & { @@ -31,7 +31,7 @@ with functions as ( ${props.schemaFilter ? `join pg_namespace n on p.pronamespace = n.oid` : ''} where ${props.schemaFilter ? `n.nspname ${props.schemaFilter} AND` : ''} - ${props.idsFilter ? `p.oid::text ${props.idsFilter} AND` : ''} + ${props.idsFilter ? `p.oid ${props.idsFilter} AND` : ''} ${props.nameFilter ? `p.proname ${props.nameFilter} AND` : ''} p.prokind = 'f' ) diff --git a/src/lib/sql/index.ts b/src/lib/sql/index.ts deleted file mode 100644 index 9ed673f1..00000000 --- a/src/lib/sql/index.ts +++ /dev/null @@ -1,42 +0,0 @@ -export type SQLQueryProps = { - limit?: number - offset?: number -} - -export type SQLQueryPropsWithSchemaFilter = SQLQueryProps & { - schemaFilter?: string -} - -export type SQLQueryPropsWithIdsFilter = SQLQueryProps & { - idsFilter?: string -} - -export type SQLQueryPropsWithSchemaFilterAndIdsFilter = SQLQueryProps & { - schemaFilter?: string - idsFilter?: string -} - -export type SQLQueryPropsWithTypes = SQLQueryPropsWithSchemaFilterAndIdsFilter & { - includeTableTypes?: boolean - includeArrayTypes?: boolean -} - -// Export all SQL functions -export { VERSION_SQL } from './version.sql.js' -export { CONFIG_SQL } from './config.sql.js' -export { EXTENSIONS_SQL } from './extensions.sql.js' -export { PUBLICATIONS_SQL } from './publications.sql.js' -export { ROLES_SQL } from './roles.sql.js' -export { SCHEMAS_SQL } from './schemas.sql.js' -export { FUNCTIONS_SQL } from './functions.sql.js' -export { INDEXES_SQL } from './indexes.sql.js' -export { POLICIES_SQL } from './policies.sql.js' -export { TRIGGERS_SQL } from './triggers.sql.js' -export { TYPES_SQL } from './types.sql.js' -export { TABLES_SQL } from './table.sql.js' -export { VIEWS_SQL } from './views.sql.js' -export { MATERIALIZED_VIEWS_SQL } from './materialized_views.sql.js' -export { FOREIGN_TABLES_SQL } from './foreign_tables.sql.js' -export { COLUMNS_SQL } from './columns.sql.js' -export { TABLE_PRIVILEGES_SQL } from './table_privileges.sql.js' -export { COLUMN_PRIVILEGES_SQL } from './column_privileges.sql.js' diff --git a/src/lib/sql/indexes.sql.ts b/src/lib/sql/indexes.sql.ts index cb8684de..5f893a8f 100644 --- a/src/lib/sql/indexes.sql.ts +++ b/src/lib/sql/indexes.sql.ts @@ -1,4 +1,4 @@ -import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './index.js' +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './common.js' export const INDEXES_SQL = (props: SQLQueryPropsWithSchemaFilterAndIdsFilter) => /* SQL */ ` SELECT diff --git a/src/lib/sql/materialized_views.sql.ts b/src/lib/sql/materialized_views.sql.ts index 8ea28396..aae179e8 100644 --- a/src/lib/sql/materialized_views.sql.ts +++ b/src/lib/sql/materialized_views.sql.ts @@ -1,7 +1,9 @@ -import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './index.js' +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './common.js' export const MATERIALIZED_VIEWS_SQL = ( - props: SQLQueryPropsWithSchemaFilterAndIdsFilter + props: SQLQueryPropsWithSchemaFilterAndIdsFilter & { + materializedViewIdentifierFilter?: string + } ) => /* SQL */ ` select c.oid::int8 as id, @@ -15,6 +17,7 @@ from where ${props.schemaFilter ? `n.nspname ${props.schemaFilter} AND` : ''} ${props.idsFilter ? `c.oid ${props.idsFilter} AND` : ''} + ${props.materializedViewIdentifierFilter ? `(n.nspname || '.' || c.relname) ${props.materializedViewIdentifierFilter} AND` : ''} c.relkind = 'm' ${props.limit ? `limit ${props.limit}` : ''} ${props.offset ? `offset ${props.offset}` : ''} diff --git a/src/lib/sql/policies.sql.ts b/src/lib/sql/policies.sql.ts index 92cfb188..9e354931 100644 --- a/src/lib/sql/policies.sql.ts +++ b/src/lib/sql/policies.sql.ts @@ -1,4 +1,4 @@ -import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './index.js' +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './common.js' export const POLICIES_SQL = ( props: SQLQueryPropsWithSchemaFilterAndIdsFilter & { functionNameIdentifierFilter?: string } diff --git a/src/lib/sql/publications.sql.ts b/src/lib/sql/publications.sql.ts index ee665394..cd04e05b 100644 --- a/src/lib/sql/publications.sql.ts +++ b/src/lib/sql/publications.sql.ts @@ -1,6 +1,8 @@ -import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './index.js' +import type { SQLQueryPropsWithIdsFilter } from './common.js' -export const PUBLICATIONS_SQL = (props: SQLQueryPropsWithSchemaFilterAndIdsFilter) => /* SQL */ ` +export const PUBLICATIONS_SQL = ( + props: SQLQueryPropsWithIdsFilter & { nameFilter?: string } +) => /* SQL */ ` SELECT p.oid :: int8 AS id, p.pubname AS name, @@ -39,6 +41,7 @@ FROM ) AS pr ON 1 = 1 WHERE ${props.idsFilter ? `p.oid ${props.idsFilter}` : 'true'} + ${props.nameFilter ? `AND p.pubname ${props.nameFilter}` : ''} ${props.limit ? `limit ${props.limit}` : ''} ${props.offset ? `offset ${props.offset}` : ''} ` diff --git a/src/lib/sql/roles.sql.ts b/src/lib/sql/roles.sql.ts index 5b730ebc..b3d29358 100644 --- a/src/lib/sql/roles.sql.ts +++ b/src/lib/sql/roles.sql.ts @@ -1,6 +1,11 @@ -import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './index.js' +import type { SQLQueryPropsWithIdsFilter } from './common.js' -export const ROLES_SQL = (props: SQLQueryPropsWithSchemaFilterAndIdsFilter) => /* SQL */ ` +export const ROLES_SQL = ( + props: SQLQueryPropsWithIdsFilter & { + includeDefaultRoles?: boolean + nameFilter?: string + } +) => /* SQL */ ` -- TODO: Consider using pg_authid vs. pg_roles for unencrypted password field SELECT oid :: int8 AS id, @@ -30,6 +35,10 @@ FROM pg_roles WHERE ${props.idsFilter ? `oid ${props.idsFilter}` : 'true'} + -- All default/predefined roles start with pg_: https://www.postgresql.org/docs/15/predefined-roles.html + -- The pg_ prefix is also reserved. + ${!props.includeDefaultRoles ? `AND NOT pg_catalog.starts_with(rolname, 'pg_')` : ''} + ${props.nameFilter ? `AND rolname ${props.nameFilter}` : ''} ${props.limit ? `limit ${props.limit}` : ''} ${props.offset ? `offset ${props.offset}` : ''} ` diff --git a/src/lib/sql/schemas.sql.ts b/src/lib/sql/schemas.sql.ts index 57b6f9c6..a9e5d85b 100644 --- a/src/lib/sql/schemas.sql.ts +++ b/src/lib/sql/schemas.sql.ts @@ -1,10 +1,9 @@ -import type { SQLQueryProps } from './index.js' +import type { SQLQueryProps } from './common.js' export const SCHEMAS_SQL = ( - props: SQLQueryProps & { nameFilter?: string; idsFilter?: string } + props: SQLQueryProps & { nameFilter?: string; idsFilter?: string; includeSystemSchemas?: boolean } ) => /* SQL */ ` -- Adapted from information_schema.schemata - select n.oid::int8 as id, n.nspname as name, @@ -16,6 +15,7 @@ where n.nspowner = u.oid ${props.idsFilter ? `and n.oid ${props.idsFilter}` : ''} ${props.nameFilter ? `and n.nspname ${props.nameFilter}` : ''} + ${!props.includeSystemSchemas ? `and not pg_catalog.starts_with(n.nspname, 'pg_')` : ''} and ( pg_has_role(n.nspowner, 'USAGE') or has_schema_privilege(n.oid, 'CREATE, USAGE') diff --git a/src/lib/sql/table.sql.ts b/src/lib/sql/table.sql.ts index af5f7158..13487ec5 100644 --- a/src/lib/sql/table.sql.ts +++ b/src/lib/sql/table.sql.ts @@ -1,4 +1,4 @@ -import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './index.js' +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './common.js' export const TABLES_SQL = ( props: SQLQueryPropsWithSchemaFilterAndIdsFilter & { tableIdentifierFilter?: string } diff --git a/src/lib/sql/table_privileges.sql.ts b/src/lib/sql/table_privileges.sql.ts index 2726476c..ca4ea122 100644 --- a/src/lib/sql/table_privileges.sql.ts +++ b/src/lib/sql/table_privileges.sql.ts @@ -1,7 +1,9 @@ -import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './index.js' +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './common.js' export const TABLE_PRIVILEGES_SQL = ( - props: SQLQueryPropsWithSchemaFilterAndIdsFilter + props: SQLQueryPropsWithSchemaFilterAndIdsFilter & { + nameIdentifierFilter?: string + } ) => /* SQL */ ` -- Despite the name \`table_privileges\`, this includes other kinds of relations: -- views, matviews, etc. "Relation privileges" just doesn't roll off the tongue. @@ -66,6 +68,7 @@ left join ( where c.relkind in ('r', 'v', 'm', 'f', 'p') ${props.schemaFilter ? `and nc.nspname ${props.schemaFilter}` : ''} ${props.idsFilter ? `and c.oid ${props.idsFilter}` : ''} + ${props.nameIdentifierFilter ? `and (nc.nspname || '.' || c.relname) ${props.nameIdentifierFilter}` : ''} and not pg_is_other_temp_schema(c.relnamespace) and ( pg_has_role(c.relowner, 'USAGE') diff --git a/src/lib/sql/table_relationships.sql.ts b/src/lib/sql/table_relationships.sql.ts index e5909eb9..d74c12a4 100644 --- a/src/lib/sql/table_relationships.sql.ts +++ b/src/lib/sql/table_relationships.sql.ts @@ -1,4 +1,6 @@ -export const TABLE_RELATIONSHIPS_SQL = (schemaFilter?: string) => /* SQL */ ` +import type { SQLQueryPropsWithSchemaFilter } from './common.js' + +export const TABLE_RELATIONSHIPS_SQL = (props: SQLQueryPropsWithSchemaFilter) => /* SQL */ ` -- Adapted from -- https://github.com/PostgREST/postgrest/blob/f9f0f79fa914ac00c11fbf7f4c558e14821e67e2/src/PostgREST/SchemaCache.hs#L722 WITH @@ -16,7 +18,7 @@ pks_uniques_cols AS ( WHERE contype IN ('p', 'u') and connamespace::regnamespace::text <> 'pg_catalog' - ${schemaFilter ? `and connamespace::regnamespace::text ${schemaFilter}` : ''} + ${props.schemaFilter ? `and connamespace::regnamespace::text ${props.schemaFilter}` : ''} GROUP BY connamespace, conrelid ) SELECT @@ -36,7 +38,7 @@ JOIN LATERAL ( FROM unnest(traint.conkey, traint.confkey) WITH ORDINALITY AS _(col, ref, ord) JOIN pg_attribute cols ON cols.attrelid = traint.conrelid AND cols.attnum = col JOIN pg_attribute refs ON refs.attrelid = traint.confrelid AND refs.attnum = ref - WHERE ${schemaFilter ? `traint.connamespace::regnamespace::text ${schemaFilter}` : 'true'} + WHERE ${props.schemaFilter ? `traint.connamespace::regnamespace::text ${props.schemaFilter}` : 'true'} ) AS column_info ON TRUE JOIN pg_namespace ns1 ON ns1.oid = traint.connamespace JOIN pg_class tab ON tab.oid = traint.conrelid @@ -45,5 +47,5 @@ JOIN pg_namespace ns2 ON ns2.oid = other.relnamespace LEFT JOIN pks_uniques_cols pks_uqs ON pks_uqs.connamespace = traint.connamespace AND pks_uqs.conrelid = traint.conrelid WHERE traint.contype = 'f' AND traint.conparentid = 0 -${schemaFilter ? `and ns1.nspname ${schemaFilter}` : ''} +${props.schemaFilter ? `and ns1.nspname ${props.schemaFilter}` : ''} ` diff --git a/src/lib/sql/triggers.sql.ts b/src/lib/sql/triggers.sql.ts index 17303fa7..5580373e 100644 --- a/src/lib/sql/triggers.sql.ts +++ b/src/lib/sql/triggers.sql.ts @@ -1,6 +1,11 @@ -import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './index.js' +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './common.js' -export const TRIGGERS_SQL = (props: SQLQueryPropsWithSchemaFilterAndIdsFilter) => /* SQL */ ` +export const TRIGGERS_SQL = ( + props: SQLQueryPropsWithSchemaFilterAndIdsFilter & { + tableNameFilter?: string + nameFilter?: string + } +) => /* SQL */ ` SELECT pg_t.oid AS id, pg_t.tgrelid AS table_id, @@ -12,7 +17,7 @@ SELECT END AS enabled_mode, ( STRING_TO_ARRAY( - ENCODE(pg_t.tgargs, 'escape'), '\x00' + ENCODE(pg_t.tgargs, 'escape'), '\\000' ) )[:pg_t.tgnargs] AS function_args, is_t.trigger_name AS name, @@ -41,6 +46,8 @@ JOIN pg_namespace AS pg_n ON pg_p.pronamespace = pg_n.oid WHERE ${props.schemaFilter ? `table_ns.nspname ${props.schemaFilter}` : 'true'} + ${props.tableNameFilter ? `AND pg_c.relname ${props.tableNameFilter}` : ''} + ${props.nameFilter ? `AND is_t.trigger_name ${props.nameFilter}` : ''} ${props.idsFilter ? `AND pg_t.oid ${props.idsFilter}` : ''} GROUP BY pg_t.oid, diff --git a/src/lib/sql/types.sql.ts b/src/lib/sql/types.sql.ts index 77447d5b..990fa22f 100644 --- a/src/lib/sql/types.sql.ts +++ b/src/lib/sql/types.sql.ts @@ -1,6 +1,11 @@ -import type { SQLQueryPropsWithTypes } from './index.js' +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './common.js' -export const TYPES_SQL = (props: SQLQueryPropsWithTypes) => /* SQL */ ` +export const TYPES_SQL = ( + props: SQLQueryPropsWithSchemaFilterAndIdsFilter & { + includeTableTypes?: boolean + includeArrayTypes?: boolean + } +) => /* SQL */ ` select t.oid::int8 as id, t.typname as name, @@ -49,7 +54,7 @@ from ) ) ${ - props.includeArrayTypes + !props.includeArrayTypes ? `and not exists ( select from diff --git a/src/lib/sql/views.sql.ts b/src/lib/sql/views.sql.ts index 85044276..95a707e2 100644 --- a/src/lib/sql/views.sql.ts +++ b/src/lib/sql/views.sql.ts @@ -1,6 +1,10 @@ -import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './index.js' +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './common.js' -export const VIEWS_SQL = (props: SQLQueryPropsWithSchemaFilterAndIdsFilter) => /* SQL */ ` +export const VIEWS_SQL = ( + props: SQLQueryPropsWithSchemaFilterAndIdsFilter & { + viewIdentifierFilter?: string + } +) => /* SQL */ ` SELECT c.oid :: int8 AS id, n.nspname AS schema, @@ -14,6 +18,7 @@ FROM WHERE ${props.schemaFilter ? `n.nspname ${props.schemaFilter} AND` : ''} ${props.idsFilter ? `c.oid ${props.idsFilter} AND` : ''} + ${props.viewIdentifierFilter ? `(n.nspname || '.' || c.relname) ${props.viewIdentifierFilter} AND` : ''} c.relkind = 'v' ${props.limit ? `limit ${props.limit}` : ''} ${props.offset ? `offset ${props.offset}` : ''} diff --git a/src/lib/sql/views_key_dependencies.sql.ts b/src/lib/sql/views_key_dependencies.sql.ts index 106424f8..31035012 100644 --- a/src/lib/sql/views_key_dependencies.sql.ts +++ b/src/lib/sql/views_key_dependencies.sql.ts @@ -1,4 +1,6 @@ -export const VIEWS_KEY_DEPENDENCIES_SQL = (schemaFilter?: string) => /* SQL */ ` +import type { SQLQueryPropsWithSchemaFilter } from './common.js' + +export const VIEWS_KEY_DEPENDENCIES_SQL = (props: SQLQueryPropsWithSchemaFilter) => /* SQL */ ` -- Adapted from -- https://github.com/PostgREST/postgrest/blob/f9f0f79fa914ac00c11fbf7f4c558e14821e67e2/src/PostgREST/SchemaCache.hs#L820 with recursive @@ -26,7 +28,7 @@ pks_fks as ( from pg_constraint left join lateral unnest(confkey) with ordinality as _(col, ord) on true where contype='f' - ${schemaFilter ? `and connamespace::regnamespace::text ${schemaFilter}` : ''} + ${props.schemaFilter ? `and connamespace::regnamespace::text ${props.schemaFilter}` : ''} ), views as ( select @@ -38,7 +40,7 @@ views as ( join pg_namespace n on n.oid = c.relnamespace join pg_rewrite r on r.ev_class = c.oid where c.relkind in ('v', 'm') - ${schemaFilter ? `and n.nspname ${schemaFilter}` : ''} + ${props.schemaFilter ? `and n.nspname ${props.schemaFilter}` : ''} ), transform_json as ( select @@ -74,19 +76,19 @@ transform_json as ( -- ----------------------------------------------- -- pattern | replacement | flags -- ----------------------------------------------- - -- \`<>\` in pg_node_tree is the same as \`null\` in JSON, but due to very poor performance of json_typeof + -- <> in pg_node_tree is the same as null in JSON, but due to very poor performance of json_typeof -- we need to make this an empty array here to prevent json_array_elements from throwing an error -- when the targetList is null. -- We'll need to put it first, to make the node protection below work for node lists that start with - -- null: \`(<> ...\`, too. This is the case for coldefexprs, when the first column does not have a default value. + -- null: (<> ..., too. This is the case for coldefexprs, when the first column does not have a default value. '<>' , '()' - -- \`,\` is not part of the pg_node_tree format, but used in the regex. - -- This removes all \`,\` that might be part of column names. + -- , is not part of the pg_node_tree format, but used in the regex. + -- This removes all , that might be part of column names. ), ',' , '' - -- The same applies for \`{\` and \`}\`, although those are used a lot in pg_node_tree. + -- The same applies for { and }, although those are used a lot in pg_node_tree. -- We remove the escaped ones, which might be part of column names again. - ), E'\\{' , '' - ), E'\\}' , '' + ), E'\\\\{' , '' + ), E'\\\\}' , '' -- The fields we need are formatted as json manually to protect them from the regex. ), ' :targetList ' , ',"targetList":' ), ' :resno ' , ',"resno":' @@ -142,7 +144,7 @@ recursion(view_id, view_schema, view_name, view_column, resorigtbl, resorigcol, false, ARRAY[resorigtbl] from results r - where ${schemaFilter ? `view_schema ${schemaFilter}` : 'true'} + where ${props.schemaFilter ? `view_schema ${props.schemaFilter}` : 'true'} union all select view.view_id, diff --git a/test/index.test.ts b/test/index.test.ts index 0341cc7c..6ca2b87e 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -7,20 +7,20 @@ import './lib/foreign-tables' import './lib/functions' import './lib/policies' import './lib/publications' -// import './lib/roles' -// import './lib/schemas' -// import './lib/secrets' -// import './lib/tables' -// import './lib/triggers' -// import './lib/types' -// import './lib/version' -// import './lib/views' -// import './server/column-privileges' -// import './server/indexes' -// import './server/materialized-views' -// import './server/query' -// import './server/ssl' -// import './server/table-privileges' -// import './server/typegen' -// import './server/result-size-limit' -// import './server/query-timeout' +import './lib/roles' +import './lib/schemas' +import './lib/secrets' +import './lib/tables' +import './lib/triggers' +import './lib/types' +import './lib/version' +import './lib/views' +import './server/column-privileges' +import './server/indexes' +import './server/materialized-views' +import './server/query' +import './server/ssl' +import './server/table-privileges' +import './server/typegen' +import './server/result-size-limit' +import './server/query-timeout' diff --git a/test/lib/tables.ts b/test/lib/tables.ts index c35546b8..00230ab4 100644 --- a/test/lib/tables.ts +++ b/test/lib/tables.ts @@ -81,39 +81,39 @@ test('list', async () => { { "check": null, "comment": null, - "data_type": "numeric", - "default_value": null, - "enums": [], - "format": "numeric", + "data_type": "USER-DEFINED", + "default_value": "'ACTIVE'::user_status", + "enums": [ + "ACTIVE", + "INACTIVE", + ], + "format": "user_status", "identity_generation": null, "is_generated": false, "is_identity": false, "is_nullable": true, "is_unique": false, "is_updatable": true, - "name": "decimal", - "ordinal_position": 4, + "name": "status", + "ordinal_position": 3, "schema": "public", "table": "users", }, { "check": null, "comment": null, - "data_type": "USER-DEFINED", - "default_value": "'ACTIVE'::user_status", - "enums": [ - "ACTIVE", - "INACTIVE", - ], - "format": "user_status", + "data_type": "numeric", + "default_value": null, + "enums": [], + "format": "numeric", "identity_generation": null, "is_generated": false, "is_identity": false, "is_nullable": true, "is_unique": false, "is_updatable": true, - "name": "status", - "ordinal_position": 3, + "name": "decimal", + "ordinal_position": 4, "schema": "public", "table": "users", }, From ae4798e950ccdfb03c0880ddb9aee153b95bea54 Mon Sep 17 00:00:00 2001 From: avallete Date: Mon, 1 Sep 2025 12:58:46 +0200 Subject: [PATCH 4/7] feat: add functions args retrieval --- schema-filter-demo.ts | 1 - src/lib/PostgresMetaColumnPrivileges.ts | 4 +-- src/lib/PostgresMetaExtensions.ts | 2 +- src/lib/PostgresMetaFunctions.ts | 45 ++++--------------------- src/lib/PostgresMetaSchemas.ts | 2 +- src/lib/sql/functions.sql.ts | 28 +++++++++++++++ 6 files changed, 39 insertions(+), 43 deletions(-) delete mode 100644 schema-filter-demo.ts diff --git a/schema-filter-demo.ts b/schema-filter-demo.ts deleted file mode 100644 index 0519ecba..00000000 --- a/schema-filter-demo.ts +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/lib/PostgresMetaColumnPrivileges.ts b/src/lib/PostgresMetaColumnPrivileges.ts index 51f306c6..b2a0b6fe 100644 --- a/src/lib/PostgresMetaColumnPrivileges.ts +++ b/src/lib/PostgresMetaColumnPrivileges.ts @@ -74,7 +74,7 @@ end $$; // Return the updated column privileges for modified columns. const columnIds = [...new Set(grants.map(({ column_id }) => column_id))] const columnIdsFilter = filterByValue(columnIds) - sql = COLUMN_PRIVILEGES_SQL({ schemaFilter: undefined, columnIdsFilter }) + sql = COLUMN_PRIVILEGES_SQL({ columnIdsFilter }) return await this.query(sql) } @@ -114,7 +114,7 @@ end $$; // Return the updated column privileges for modified columns. const columnIds = [...new Set(revokes.map(({ column_id }) => column_id))] const columnIdsFilter = filterByValue(columnIds) - sql = COLUMN_PRIVILEGES_SQL({ schemaFilter: undefined, columnIdsFilter }) + sql = COLUMN_PRIVILEGES_SQL({ columnIdsFilter }) return await this.query(sql) } } diff --git a/src/lib/PostgresMetaExtensions.ts b/src/lib/PostgresMetaExtensions.ts index 598c2e0b..9543dc2c 100644 --- a/src/lib/PostgresMetaExtensions.ts +++ b/src/lib/PostgresMetaExtensions.ts @@ -22,7 +22,7 @@ export default class PostgresMetaExtensions { } async retrieve({ name }: { name: string }): Promise> { - const nameFilter = filterByValue([`${name}`]) + const nameFilter = filterByValue([name]) const sql = EXTENSIONS_SQL({ nameFilter }) const { data, error } = await this.query(sql) if (error) { diff --git a/src/lib/PostgresMetaFunctions.ts b/src/lib/PostgresMetaFunctions.ts index 643638e0..b6e2a39c 100644 --- a/src/lib/PostgresMetaFunctions.ts +++ b/src/lib/PostgresMetaFunctions.ts @@ -68,7 +68,7 @@ export default class PostgresMetaFunctions { } } else if (name && schema && args) { const nameFilter = filterByValue([name]) - const sql = this.generateRetrieveFunctionSql({ schemaFilter, nameFilter, schema, name, args }) + const sql = FUNCTIONS_SQL({ schemaFilter, nameFilter, args: args.map(literal) }) const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -163,6 +163,11 @@ export default class PostgresMetaFunctions { )}(${identityArgs}) SET SCHEMA ${ident(schema)};` : '' + const currentSchemaFilter = currentFunc!.schema + ? filterByList([currentFunc!.schema], []) + : undefined + const currentNameFilter = currentFunc!.name ? filterByValue([currentFunc!.name]) : undefined + const sql = ` DO LANGUAGE plpgsql $$ BEGIN @@ -171,7 +176,7 @@ export default class PostgresMetaFunctions { IF ( SELECT id - FROM (${FUNCTIONS_SQL({})}) AS f + FROM (${FUNCTIONS_SQL({ schemaFilter: currentSchemaFilter, nameFilter: currentNameFilter })}) AS f WHERE f.schema = ${literal(currentFunc!.schema)} AND f.name = ${literal(currentFunc!.name)} AND f.identity_argument_types = ${literal(identityArgs)} @@ -256,40 +261,4 @@ export default class PostgresMetaFunctions { }; ` } - - private generateRetrieveFunctionSql({ - schemaFilter, - nameFilter, - args, - }: { - schemaFilter?: string - nameFilter?: string - schema: string - name: string - args: string[] - }): string { - return `${FUNCTIONS_SQL({ schemaFilter, nameFilter })} JOIN pg_proc AS p ON f.oid = p.oid WHERE p.proargtypes::text = ${ - args.length - ? `( - SELECT STRING_AGG(type_oid::text, ' ') FROM ( - SELECT ( - split_args.arr[ - array_length( - split_args.arr, - 1 - ) - ]::regtype::oid - ) AS type_oid FROM ( - SELECT STRING_TO_ARRAY( - UNNEST( - ARRAY[${args.map(literal)}] - ), - ' ' - ) AS arr - ) AS split_args - ) args - )` - : literal('') - }` - } } diff --git a/src/lib/PostgresMetaSchemas.ts b/src/lib/PostgresMetaSchemas.ts index 120ca5ac..aa17bcfd 100644 --- a/src/lib/PostgresMetaSchemas.ts +++ b/src/lib/PostgresMetaSchemas.ts @@ -59,7 +59,7 @@ export default class PostgresMetaSchemas { return { data: data[0], error } } } else if (name) { - const nameFilter = filterByValue([`${name}`]) + const nameFilter = filterByValue([name]) const sql = SCHEMAS_SQL({ nameFilter }) const { data, error } = await this.query(sql) if (error) { diff --git a/src/lib/sql/functions.sql.ts b/src/lib/sql/functions.sql.ts index 10b52382..330352d2 100644 --- a/src/lib/sql/functions.sql.ts +++ b/src/lib/sql/functions.sql.ts @@ -3,6 +3,7 @@ import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './common.js' export const FUNCTIONS_SQL = ( props: SQLQueryPropsWithSchemaFilterAndIdsFilter & { nameFilter?: string + args?: string[] } ) => /* SQL */ ` -- CTE with sane arg_modes, arg_names, and arg_types. @@ -33,6 +34,33 @@ with functions as ( ${props.schemaFilter ? `n.nspname ${props.schemaFilter} AND` : ''} ${props.idsFilter ? `p.oid ${props.idsFilter} AND` : ''} ${props.nameFilter ? `p.proname ${props.nameFilter} AND` : ''} + ${ + props.args && props.args.length > 0 + ? `p.proargtypes::text = ${ + props.args.length + ? `( + SELECT STRING_AGG(type_oid::text, ' ') FROM ( + SELECT ( + split_args.arr[ + array_length( + split_args.arr, + 1 + ) + ]::regtype::oid + ) AS type_oid FROM ( + SELECT STRING_TO_ARRAY( + UNNEST( + ARRAY[${props.args}] + ), + ' ' + ) AS arr + ) AS split_args + ) args + )` + : "''" + } AND` + : '' + } p.prokind = 'f' ) select From 82ae355dcfe2687100cda45ca0a175a39c893e3a Mon Sep 17 00:00:00 2001 From: avallete Date: Mon, 1 Sep 2025 14:09:54 +0200 Subject: [PATCH 5/7] chore: fix functions --- src/lib/sql/functions.sql.ts | 16 +++++++++------- src/lib/sql/version.sql.ts | 1 - 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/lib/sql/functions.sql.ts b/src/lib/sql/functions.sql.ts index 330352d2..92715b95 100644 --- a/src/lib/sql/functions.sql.ts +++ b/src/lib/sql/functions.sql.ts @@ -35,10 +35,12 @@ with functions as ( ${props.idsFilter ? `p.oid ${props.idsFilter} AND` : ''} ${props.nameFilter ? `p.proname ${props.nameFilter} AND` : ''} ${ - props.args && props.args.length > 0 - ? `p.proargtypes::text = ${ - props.args.length - ? `( + props.args === undefined + ? '' + : props.args.length > 0 + ? `p.proargtypes::text = ${ + props.args.length + ? `( SELECT STRING_AGG(type_oid::text, ' ') FROM ( SELECT ( split_args.arr[ @@ -57,9 +59,9 @@ with functions as ( ) AS split_args ) args )` - : "''" - } AND` - : '' + : "''" + } AND` + : '' } p.prokind = 'f' ) diff --git a/src/lib/sql/version.sql.ts b/src/lib/sql/version.sql.ts index adfa193f..f959c5fd 100644 --- a/src/lib/sql/version.sql.ts +++ b/src/lib/sql/version.sql.ts @@ -9,5 +9,4 @@ SELECT pg_stat_activity ) AS active_connections, current_setting('max_connections') :: int8 AS max_connections - ` From f8434bee0314bc2046416d56f606a58f0328a9f4 Mon Sep 17 00:00:00 2001 From: avallete Date: Tue, 2 Sep 2025 11:53:49 +0200 Subject: [PATCH 6/7] fix(table): use or for table relationships --- src/lib/sql/table.sql.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib/sql/table.sql.ts b/src/lib/sql/table.sql.ts index 13487ec5..d7f70331 100644 --- a/src/lib/sql/table.sql.ts +++ b/src/lib/sql/table.sql.ts @@ -80,10 +80,8 @@ FROM join pg_namespace nta on cta.relnamespace = nta.oid ) on ta.attrelid = c.confrelid and ta.attnum = any (c.confkey) where - ${props.schemaFilter ? `nsa.nspname ${props.schemaFilter} AND` : ''} - ${props.schemaFilter ? `nta.nspname ${props.schemaFilter} AND` : ''} - ${props.tableIdentifierFilter ? `nsa.nspname || '.' || csa.relname ${props.tableIdentifierFilter} AND` : ''} - ${props.tableIdentifierFilter ? `nta.nspname || '.' || cta.relname ${props.tableIdentifierFilter} AND` : ''} + ${props.schemaFilter ? `nsa.nspname ${props.schemaFilter} OR nta.nspname ${props.schemaFilter} AND` : ''} + ${props.tableIdentifierFilter ? `(nsa.nspname || '.' || csa.relname) ${props.tableIdentifierFilter} OR (nta.nspname || '.' || cta.relname) ${props.tableIdentifierFilter} AND` : ''} c.contype = 'f' ) as relationships on (relationships.source_schema = nc.nspname and relationships.source_table_name = c.relname) From 626efbca8fee8165a58ed4f98ab03f2dc2b1bec6 Mon Sep 17 00:00:00 2001 From: avallete Date: Tue, 2 Sep 2025 11:54:27 +0200 Subject: [PATCH 7/7] test(functions): add tests for retrival argument based --- test/lib/functions.ts | 49 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/test/lib/functions.ts b/test/lib/functions.ts index 05de3244..fb2c4692 100644 --- a/test/lib/functions.ts +++ b/test/lib/functions.ts @@ -354,3 +354,52 @@ test('retrieve set-returning function', async () => { ` ) }) + +test('retrieve function by args filter - polymorphic function with text argument', async () => { + const res = await pgMeta.functions.retrieve({ + schema: 'public', + name: 'polymorphic_function', + args: ['text'], + }) + expect(res.data).toMatchObject({ + name: 'polymorphic_function', + schema: 'public', + argument_types: 'text', + args: [ + { type_id: 25, mode: 'in' }, // text type_id is 25 + ], + }) + expect(res.error).toBeNull() +}) + +test('retrieve function by args filter - polymorphic function with boolean argument', async () => { + const res = await pgMeta.functions.retrieve({ + schema: 'public', + name: 'polymorphic_function', + args: ['boolean'], + }) + expect(res.data).toMatchObject({ + name: 'polymorphic_function', + schema: 'public', + argument_types: 'boolean', + args: [ + { type_id: 16, mode: 'in' }, // boolean type_id is 16 + ], + }) + expect(res.error).toBeNull() +}) + +test('retrieve function by args filter - function with no arguments', async () => { + const res = await pgMeta.functions.retrieve({ + schema: 'public', + name: 'function_returning_set_of_rows', + args: [], + }) + expect(res.data).toMatchObject({ + name: 'function_returning_set_of_rows', + schema: 'public', + argument_types: '', + args: [], + }) + expect(res.error).toBeNull() +})