diff --git a/src/lib/PostgresMetaColumnPrivileges.ts b/src/lib/PostgresMetaColumnPrivileges.ts index 4df0d39a..b2a0b6fe 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({ 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({ columnIdsFilter }) return await this.query(sql) } } diff --git a/src/lib/PostgresMetaColumns.ts b/src/lib/PostgresMetaColumns.ts index 15e56507..613c8ea2 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 { filterByValue, filterByList } from './helpers.js' +import { COLUMNS_SQL } from './sql/columns.sql.js' export default class PostgresMetaColumns { query: (sql: string) => Promise> @@ -29,32 +29,13 @@ export default class PostgresMetaColumns { limit?: number offset?: number } = {}): Promise> { - let sql = ` -WITH - columns AS (${columnsSql}) -SELECT - * -FROM - columns -WHERE - true` - const filter = filterByList( + const schemaFilter = filterByList( includedSchemas, excludedSchemas, !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined ) - if (filter) { - sql += ` AND schema ${filter}` - } - 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) } @@ -79,6 +60,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 +68,8 @@ 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 idsFilter = filterByValue([`${tableId}.${ordinalPos}`]) + const sql = COLUMNS_SQL({ idsFilter }) const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -96,9 +79,8 @@ WHERE return { data: data[0], error } } } else if (name && table) { - const sql = `${columnsSql} AND a.attname = ${literal(name)} AND c.relname = ${literal( - table - )} AND nc.nspname = ${literal(schema)};` + 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..35b194d8 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/config.sql.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}` - } + const sql = CONFIG_SQL({ limit, offset }) return await this.query(sql) } } diff --git a/src/lib/PostgresMetaExtensions.ts b/src/lib/PostgresMetaExtensions.ts index 4589057f..9543dc2c 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/extensions.sql.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..e565da43 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,17 +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}` - } - 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) } @@ -69,9 +60,11 @@ export default class PostgresMetaForeignTables { schema?: string }): Promise> { 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 +74,11 @@ 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 - )};` + tableIdentifierFilter: nameFilter, + }) const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -103,9 +96,23 @@ export default class PostgresMetaForeignTables { } } -const generateEnrichedForeignTablesSql = ({ includeColumns }: { includeColumns: boolean }) => ` -with foreign_tables as (${foreignTablesSql}) - ${includeColumns ? `, columns as (${columnsSql})` : ''} +const generateEnrichedForeignTablesSql = ({ + includeColumns, + 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, 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 b50e6761..b6e2a39c 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/functions.sql.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}` - } + const 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 = FUNCTIONS_SQL({ schemaFilter, nameFilter, args: args.map(literal) }) const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -169,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 @@ -177,7 +176,7 @@ export default class PostgresMetaFunctions { IF ( SELECT id - FROM (${functionsSql}) 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)} @@ -262,49 +261,4 @@ export default class PostgresMetaFunctions { }; ` } - - private generateRetrieveFunctionSql({ - schema, - name, - args, - }: { - 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 = ${ - 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('') - }` - } } - -const enrichedFunctionsSql = ` - WITH f AS ( - ${functionsSql} - ) - SELECT - f.* - FROM f -` diff --git a/src/lib/PostgresMetaIndexes.ts b/src/lib/PostgresMetaIndexes.ts index 14ffbba7..84f7f100 100644 --- a/src/lib/PostgresMetaIndexes.ts +++ b/src/lib/PostgresMetaIndexes.ts @@ -1,10 +1,9 @@ -import { ident, literal } from 'pg-format' import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' -import { filterByList } from './helpers.js' -import { indexesSql } from './sql/index.js' +import { filterByList, filterByValue } from './helpers.js' import { PostgresMetaResult, PostgresIndex } from './types.js' +import { INDEXES_SQL } from './sql/indexes.sql.js' -export default class PostgresMetaFunctions { +export default class PostgresMetaIndexes { query: (sql: string) => Promise> constructor(query: (sql: string) => Promise>) { @@ -24,21 +23,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}` - } + const sql = INDEXES_SQL({ schemaFilter, limit, offset }) return await this.query(sql) } @@ -54,13 +44,13 @@ export default class PostgresMetaFunctions { }): Promise> async retrieve({ id, - args = [], }: { id?: number args?: string[] }): Promise> { if (id) { - const sql = `${enrichedSql} 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 } @@ -74,12 +64,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..0a32793a 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, filterByValue } from './helpers.js' import { PostgresMetaResult, PostgresMaterializedView } from './types.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> @@ -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) } @@ -69,9 +46,11 @@ export default class PostgresMetaMaterializedViews { schema?: string }): Promise> { if (id) { - const sql = `${generateEnrichedMaterializedViewsSql({ + const idsFilter = filterByValue([id]) + const sql = generateEnrichedMaterializedViewsSql({ includeColumns: true, - })} where materialized_views.id = ${literal(id)};` + idsFilter, + }) const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -81,11 +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, - })} 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 } @@ -103,9 +82,23 @@ export default class PostgresMetaMaterializedViews { } } -const generateEnrichedMaterializedViewsSql = ({ includeColumns }: { includeColumns: boolean }) => ` -with materialized_views as (${materializedViewsSql}) - ${includeColumns ? `, columns as (${columnsSql})` : ''} +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, 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 fa476c12..72d3157b 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/policies.sql.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..f3fdc549 100644 --- a/src/lib/PostgresMetaPublications.ts +++ b/src/lib/PostgresMetaPublications.ts @@ -1,6 +1,7 @@ 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/publications.sql.js' +import { filterByValue } from './helpers.js' export default class PostgresMetaPublications { query: (sql: string) => Promise> @@ -16,13 +17,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 +31,8 @@ export default class PostgresMetaPublications { name?: string }): Promise> { if (id) { - const sql = `${publicationsSql} 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 } @@ -46,7 +42,8 @@ export default class PostgresMetaPublications { return { data: data[0], error } } } else if (name) { - const sql = `${publicationsSql} 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 } @@ -223,7 +220,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..e4e47d60 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 schemaFilter = filterByList( + includedSchemas, + excludedSchemas, + !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined + ) let allTableM2oAndO2oRelationships: PostgresRelationship[] { - let sql = tableRelationshipsSql + const sql = TABLE_RELATIONSHIPS_SQL({ schemaFilter }) const { data, error } = (await this.query(sql)) as PostgresMetaResult if (error) { return { data: null, error } @@ -45,8 +59,9 @@ export default class PostgresMetaRelationships { column_dependencies: ColDep[] } + const viewsKeyDependenciesSql = VIEWS_KEY_DEPENDENCIES_SQL({ schemaFilter }) const { data: viewsKeyDependencies, error } = (await this.query( - allViewsKeyDependenciesSql + viewsKeyDependenciesSql )) as PostgresMetaResult if (error) { return { data: null, error } @@ -62,8 +77,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 +162,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..537b0622 100644 --- a/src/lib/PostgresMetaRoles.ts +++ b/src/lib/PostgresMetaRoles.ts @@ -1,11 +1,12 @@ import { ident, literal } from 'pg-format' -import { rolesSql } 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,32 +33,7 @@ export default class PostgresMetaRoles { limit?: number offset?: number } = {}): Promise> { - let sql = ` -WITH - roles AS (${rolesSql}) -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_')` - } - if (limit) { - sql += ` LIMIT ${limit}` - } - if (offset) { - sql += ` OFFSET ${offset}` - } + const sql = ROLES_SQL({ limit, offset, includeDefaultRoles }) const result = await this.query(sql) if (result.data) { result.data = result.data.map((role: any) => { @@ -78,7 +54,8 @@ WHERE name?: string }): Promise> { if (id) { - const sql = `${rolesSql} WHERE oid = ${literal(id)};` + const idsFilter = filterByValue([id]) + const sql = ROLES_SQL({ idsFilter }) const { data, error } = await this.query(sql) if (error) { @@ -90,7 +67,8 @@ WHERE return { data: data[0], error } } } else if (name) { - const sql = `${rolesSql} 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 b84a64cc..aa17bcfd 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 { ident } from 'pg-format' +import { SCHEMAS_SQL } from './sql/schemas.sql.js' import { PostgresMetaResult, PostgresSchema, PostgresSchemaCreate, PostgresSchemaUpdate, } from './types.js' +import { filterByList, filterByValue } from './helpers.js' +import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' export default class PostgresMetaSchemas { query: (sql: string) => Promise> @@ -16,24 +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 = schemasSql - 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}` - } + const schemaFilter = filterByList( + includedSchemas, + excludedSchemas, + !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined + ) + const sql = SCHEMAS_SQL({ limit, offset, includeSystemSchemas, nameFilter: schemaFilter }) return await this.query(sql) } @@ -47,7 +48,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 +59,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..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 { tablePrivilegesSql } from './sql/index.js' +import { filterByList, filterByValue } from './helpers.js' import { PostgresMetaResult, PostgresTablePrivileges, PostgresTablePrivilegesGrant, PostgresTablePrivilegesRevoke, } from './types.js' +import { TABLE_PRIVILEGES_SQL } from './sql/table_privileges.sql.js' export default class PostgresMetaTablePrivileges { query: (sql: string) => Promise> @@ -29,25 +29,12 @@ 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}` - } + const sql = TABLE_PRIVILEGES_SQL({ schemaFilter, limit, offset }) return await this.query(sql) } @@ -69,11 +56,8 @@ from table_privileges schema?: string }): Promise> { if (id) { - const sql = ` -with table_privileges as (${tablePrivilegesSql}) -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 } @@ -83,13 +67,8 @@ where table_privileges.relation_id = ${literal(id)};` return { data: data[0], error } } } else if (name) { - const sql = ` -with table_privileges as (${tablePrivilegesSql}) -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 } @@ -129,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 (${tablePrivilegesSql}) -select * -from table_privileges -where relation_id in (${relationIds.map(literal).join(',')}) -` + sql = TABLE_PRIVILEGES_SQL({ idsFilter: filterByList(relationIds) }) return await this.query(sql) } @@ -159,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 (${tablePrivilegesSql}) -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 5b97c253..8d3d9a47 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 { 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' export default class PostgresMetaTables { query: (sql: string) => Promise> @@ -47,21 +48,12 @@ export default class PostgresMetaTables { offset?: number includeColumns?: boolean } = {}): Promise> { - let sql = generateEnrichedTablesSql({ 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}` - } + const sql = generateEnrichedTablesSql({ includeColumns, schemaFilter, limit, offset }) return await this.query(sql) } @@ -82,10 +74,14 @@ export default class PostgresMetaTables { name?: string schema?: string }): Promise> { + const schemaFilter = schema ? filterByList([schema], []) : undefined if (id) { - const sql = `${generateEnrichedTablesSql({ + const idsFilter = filterByValue([id]) + const sql = generateEnrichedTablesSql({ + schemaFilter, includeColumns: true, - })} where tables.id = ${literal(id)};` + idsFilter, + }) const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -95,9 +91,12 @@ export default class PostgresMetaTables { return { data: data[0], error } } } else if (name) { - const sql = `${generateEnrichedTablesSql({ + 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 } @@ -247,9 +246,23 @@ COMMIT;` } } -const generateEnrichedTablesSql = ({ includeColumns }: { includeColumns: boolean }) => ` -with tables as (${tablesSql}) - ${includeColumns ? `, columns as (${columnsSql})` : ''} +const generateEnrichedTablesSql = ({ + includeColumns, + 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, 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 5ce05f76..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 { triggersSql } from './sql/index.js' +import { filterByList, filterByValue } from './helpers.js' import { PostgresMetaResult, PostgresTrigger } from './types.js' +import { TRIGGERS_SQL } from './sql/triggers.sql.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,10 @@ 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 idsFilter = filterByValue([id]) + const sql = TRIGGERS_SQL({ idsFilter }) const { data, error } = await this.query(sql) @@ -82,9 +75,9 @@ export default class PostgresMetaTriggers { } if (name && schema && table) { - const sql = `${enrichedTriggersSql} 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) @@ -168,7 +161,6 @@ export default class PostgresMetaTriggers { if (error) { return { data: null, error } } - return await this.retrieve({ name, 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..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 { typesSql } from './sql/index.js' import { PostgresMetaResult, PostgresType } from './types.js' +import { TYPES_SQL } from './sql/types.sql.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}` - } + 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 38e0299f..5ea23f37 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/version.sql.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..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 { columnsSql, viewsSql } from './sql/index.js' +import { coalesceRowsToArray, filterByList, filterByValue } from './helpers.js' import { PostgresMetaResult, PostgresView } from './types.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> @@ -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}` - } + const sql = generateEnrichedViewsSql({ includeColumns, schemaFilter, limit, offset }) return await this.query(sql) } @@ -78,9 +53,11 @@ export default class PostgresMetaViews { schema?: string }): Promise> { if (id) { - const sql = `${generateEnrichedViewsSql({ + const idsFilter = filterByValue([id]) + const sql = generateEnrichedViewsSql({ includeColumns: true, - })} where views.id = ${literal(id)};` + idsFilter, + }) const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -90,9 +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, - })} where views.name = ${literal(name)} and views.schema = ${literal(schema)};` + viewIdentifierFilter, + }) const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -110,9 +89,23 @@ export default class PostgresMetaViews { } } -const generateEnrichedViewsSql = ({ includeColumns }: { includeColumns: boolean }) => ` -with views as (${viewsSql}) - ${includeColumns ? `, columns as (${columnsSql})` : ''} +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, 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 c916a44c..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,20 +82,26 @@ 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 } } - 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: excludedSchemas.length > 0 ? excludedSchemas : undefined, + includeSystemSchemas: false, + }) if (relationshipsError) { return { data: null, error: relationshipsError } } 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 } diff --git a/src/lib/helpers.ts b/src/lib/helpers.ts index 7145bb40..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 ?? []) } @@ -24,3 +28,10 @@ export const filterByList = (include?: string[], exclude?: string[], defaultExcl } return '' } + +export const filterByValue = (ids?: (string | number)[]) => { + 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..f60101dc 100644 --- a/src/lib/sql/column_privileges.sql +++ b/src/lib/sql/column_privileges.sql.ts @@ -1,3 +1,10 @@ +import type { SQLQueryPropsWithSchemaFilter } from './common.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 b/src/lib/sql/columns.sql.ts similarity index 80% rename from src/lib/sql/columns.sql rename to src/lib/sql/columns.sql.ts index ad01e22a..d19c968c 100644 --- a/src/lib/sql/columns.sql +++ b/src/lib/sql/columns.sql.ts @@ -1,3 +1,13 @@ +import type { SQLQueryPropsWithSchemaFilter } from './common.js' + +export const COLUMNS_SQL = ( + props: SQLQueryPropsWithSchemaFilter & { + tableIdFilter?: string + tableIdentifierFilter?: string + columnNameFilter?: string + idsFilter?: string + } +) => /* SQL */ ` -- Adapted from information_schema.columns SELECT @@ -97,6 +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 + ${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 @@ -109,3 +124,6 @@ WHERE 'SELECT, INSERT, UPDATE, REFERENCES' ) ) +${props.limit ? `limit ${props.limit}` : ''} +${props.offset ? `offset ${props.offset}` : ''} +` 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 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..f33305d5 100644 --- a/src/lib/sql/config.sql +++ b/src/lib/sql/config.sql.ts @@ -1,3 +1,6 @@ +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './common.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..fe65b0c2 --- /dev/null +++ b/src/lib/sql/extensions.sql.ts @@ -0,0 +1,19 @@ +import type { SQLQueryProps } from './common.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 new file mode 100644 index 00000000..00541f0f --- /dev/null +++ b/src/lib/sql/foreign_tables.sql.ts @@ -0,0 +1,25 @@ +import type { SQLQueryProps } from './common.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, + c.relname AS name, + obj_description(c.oid) AS comment +FROM + pg_class c + JOIN pg_namespace n ON n.oid = c.relnamespace +WHERE + ${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.ts similarity index 70% rename from src/lib/sql/functions.sql rename to src/lib/sql/functions.sql.ts index d2258402..92715b95 100644 --- a/src/lib/sql/functions.sql +++ b/src/lib/sql/functions.sql.ts @@ -1,9 +1,17 @@ +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. -- 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, @@ -21,7 +29,40 @@ with functions as ( array_fill(true, array[pronargdefaults])) as arg_has_defaults from pg_proc as p + ${props.schemaFilter ? `join pg_namespace n on p.pronamespace = n.oid` : ''} where + ${props.schemaFilter ? `n.nspname ${props.schemaFilter} AND` : ''} + ${props.idsFilter ? `p.oid ${props.idsFilter} AND` : ''} + ${props.nameFilter ? `p.proname ${props.nameFilter} AND` : ''} + ${ + props.args === undefined + ? '' + : 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 @@ -105,3 +146,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 deleted file mode 100644 index 64be3aa8..00000000 --- a/src/lib/sql/index.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { readFile } from 'node:fs/promises' -import { dirname, join } from 'node:path' -import { fileURLToPath } from 'node:url' - -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 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 = await readFile(join(__dirname, 'tables.sql'), 'utf-8') -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') 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..5f893a8f 100644 --- a/src/lib/sql/indexes.sql +++ b/src/lib/sql/indexes.sql.ts @@ -1,3 +1,6 @@ +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './common.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 new file mode 100644 index 00000000..aae179e8 --- /dev/null +++ b/src/lib/sql/materialized_views.sql.ts @@ -0,0 +1,24 @@ +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './common.js' + +export const MATERIALIZED_VIEWS_SQL = ( + props: SQLQueryPropsWithSchemaFilterAndIdsFilter & { + materializedViewIdentifierFilter?: 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 + ${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 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..9e354931 100644 --- a/src/lib/sql/policies.sql +++ b/src/lib/sql/policies.sql.ts @@ -1,3 +1,8 @@ +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './common.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 67% rename from src/lib/sql/publications.sql rename to src/lib/sql/publications.sql.ts index ed0a2e20..cd04e05b 100644 --- a/src/lib/sql/publications.sql +++ b/src/lib/sql/publications.sql.ts @@ -1,3 +1,8 @@ +import type { SQLQueryPropsWithIdsFilter } from './common.js' + +export const PUBLICATIONS_SQL = ( + props: SQLQueryPropsWithIdsFilter & { nameFilter?: string } +) => /* SQL */ ` SELECT p.oid :: int8 AS id, p.pubname AS name, @@ -34,3 +39,9 @@ FROM WHERE pr.prpubid = p.oid ) 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 b/src/lib/sql/roles.sql.ts similarity index 52% rename from src/lib/sql/roles.sql rename to src/lib/sql/roles.sql.ts index a0c79d6f..b3d29358 100644 --- a/src/lib/sql/roles.sql +++ b/src/lib/sql/roles.sql.ts @@ -1,3 +1,11 @@ +import type { SQLQueryPropsWithIdsFilter } from './common.js' + +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, @@ -25,3 +33,12 @@ SELECT rolconfig AS config 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 b/src/lib/sql/schemas.sql deleted file mode 100644 index a4859fff..00000000 --- a/src/lib/sql/schemas.sql +++ /dev/null @@ -1,17 +0,0 @@ --- Adapted from information_schema.schemata - -select - n.oid::int8 as id, - n.nspname as name, - u.rolname as owner -from - pg_namespace n, - pg_roles u -where - n.nspowner = u.oid - 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_') diff --git a/src/lib/sql/schemas.sql.ts b/src/lib/sql/schemas.sql.ts new file mode 100644 index 00000000..a9e5d85b --- /dev/null +++ b/src/lib/sql/schemas.sql.ts @@ -0,0 +1,27 @@ +import type { SQLQueryProps } from './common.js' + +export const SCHEMAS_SQL = ( + props: SQLQueryProps & { nameFilter?: string; idsFilter?: string; includeSystemSchemas?: boolean } +) => /* SQL */ ` +-- Adapted from information_schema.schemata +select + n.oid::int8 as id, + n.nspname as name, + u.rolname as owner +from + pg_namespace n, + pg_roles u +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') + ) + 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/tables.sql b/src/lib/sql/table.sql.ts similarity index 73% rename from src/lib/sql/tables.sql rename to src/lib/sql/table.sql.ts index d0bb9df3..d7f70331 100644 --- a/src/lib/sql/tables.sql +++ b/src/lib/sql/table.sql.ts @@ -1,3 +1,8 @@ +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './common.js' + +export const TABLES_SQL = ( + props: SQLQueryPropsWithSchemaFilterAndIdsFilter & { tableIdentifierFilter?: string } +) => /* SQL */ ` SELECT c.oid :: int8 AS id, nc.nspname AS schema, @@ -41,6 +46,8 @@ FROM pg_attribute a, pg_namespace n where + ${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 @@ -73,11 +80,16 @@ 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} 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) or (relationships.target_table_schema = nc.nspname and relationships.target_table_name = c.relname) WHERE + ${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 ( @@ -96,3 +108,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 74% rename from src/lib/sql/table_privileges.sql rename to src/lib/sql/table_privileges.sql.ts index 435409dc..ca4ea122 100644 --- a/src/lib/sql/table_privileges.sql +++ b/src/lib/sql/table_privileges.sql.ts @@ -1,4 +1,11 @@ --- Despite the name `table_privileges`, this includes other kinds of relations: +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './common.js' + +export const TABLE_PRIVILEGES_SQL = ( + 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. -- -- For each relation, get its relacl in a jsonb format, @@ -59,6 +66,9 @@ 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}` : ''} + ${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') @@ -73,3 +83,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 80% rename from src/lib/sql/table_relationships.sql rename to src/lib/sql/table_relationships.sql.ts index 53b80ded..d74c12a4 100644 --- a/src/lib/sql/table_relationships.sql +++ b/src/lib/sql/table_relationships.sql.ts @@ -1,3 +1,6 @@ +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 @@ -15,6 +18,7 @@ pks_uniques_cols AS ( WHERE contype IN ('p', 'u') and connamespace::regnamespace::text <> 'pg_catalog' + ${props.schemaFilter ? `and connamespace::regnamespace::text ${props.schemaFilter}` : ''} GROUP BY connamespace, conrelid ) SELECT @@ -34,6 +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 ${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 @@ -42,3 +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 +${props.schemaFilter ? `and ns1.nspname ${props.schemaFilter}` : ''} +` diff --git a/src/lib/sql/triggers.sql b/src/lib/sql/triggers.sql.ts similarity index 62% rename from src/lib/sql/triggers.sql rename to src/lib/sql/triggers.sql.ts index 09fcef14..5580373e 100644 --- a/src/lib/sql/triggers.sql +++ b/src/lib/sql/triggers.sql.ts @@ -1,3 +1,11 @@ +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './common.js' + +export const TRIGGERS_SQL = ( + props: SQLQueryPropsWithSchemaFilterAndIdsFilter & { + tableNameFilter?: string + nameFilter?: string + } +) => /* SQL */ ` SELECT pg_t.oid AS id, pg_t.tgrelid AS table_id, @@ -6,10 +14,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'), '\\000' ) )[:pg_t.tgnargs] AS function_args, is_t.trigger_name AS name, @@ -26,6 +34,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 +44,11 @@ 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.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, pg_t.tgrelid, @@ -48,3 +63,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 deleted file mode 100644 index 7a628ed1..00000000 --- a/src/lib/sql/types.sql +++ /dev/null @@ -1,35 +0,0 @@ -select - t.oid::int8 as id, - t.typname as name, - n.nspname as schema, - format_type (t.oid, null) as format, - coalesce(t_enums.enums, '[]') as enums, - coalesce(t_attributes.attributes, '[]') as attributes, - obj_description (t.oid, 'pg_type') as comment -from - pg_type t - left join pg_namespace n on n.oid = t.typnamespace - left join ( - select - enumtypid, - jsonb_agg(enumlabel order by enumsortorder) as enums - from - pg_enum - group by - enumtypid - ) as t_enums on t_enums.enumtypid = t.oid - left join ( - select - oid, - jsonb_agg( - jsonb_build_object('name', a.attname, 'type_id', a.atttypid::int8) - order by a.attnum asc - ) as attributes - from - pg_class c - join pg_attribute a on a.attrelid = c.oid - where - c.relkind = 'c' and not a.attisdropped - group by - c.oid - ) as t_attributes on t_attributes.oid = t.typrelid diff --git a/src/lib/sql/types.sql.ts b/src/lib/sql/types.sql.ts new file mode 100644 index 00000000..990fa22f --- /dev/null +++ b/src/lib/sql/types.sql.ts @@ -0,0 +1,72 @@ +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './common.js' + +export const TYPES_SQL = ( + props: SQLQueryPropsWithSchemaFilterAndIdsFilter & { + includeTableTypes?: boolean + includeArrayTypes?: boolean + } +) => /* SQL */ ` +select + t.oid::int8 as id, + t.typname as name, + n.nspname as schema, + format_type (t.oid, null) as format, + coalesce(t_enums.enums, '[]') as enums, + coalesce(t_attributes.attributes, '[]') as attributes, + obj_description (t.oid, 'pg_type') as comment +from + pg_type t + left join pg_namespace n on n.oid = t.typnamespace + left join ( + select + enumtypid, + jsonb_agg(enumlabel order by enumsortorder) as enums + from + pg_enum + group by + enumtypid + ) as t_enums on t_enums.enumtypid = t.oid + left join ( + select + oid, + jsonb_agg( + jsonb_build_object('name', a.attname, 'type_id', a.atttypid::int8) + order by a.attnum asc + ) as attributes + from + pg_class c + join pg_attribute a on a.attrelid = c.oid + where + c.relkind = 'c' and not a.attisdropped + 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..f959c5fd 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,4 @@ 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 new file mode 100644 index 00000000..95a707e2 --- /dev/null +++ b/src/lib/sql/views.sql.ts @@ -0,0 +1,25 @@ +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './common.js' + +export const VIEWS_SQL = ( + props: SQLQueryPropsWithSchemaFilterAndIdsFilter & { + viewIdentifierFilter?: 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 + ${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 b/src/lib/sql/views_key_dependencies.sql.ts similarity index 78% rename from src/lib/sql/views_key_dependencies.sql rename to src/lib/sql/views_key_dependencies.sql.ts index c8534486..31035012 100644 --- a/src/lib/sql/views_key_dependencies.sql +++ b/src/lib/sql/views_key_dependencies.sql.ts @@ -1,3 +1,6 @@ +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 @@ -25,6 +28,7 @@ pks_fks as ( from pg_constraint left join lateral unnest(confkey) with ordinality as _(col, ord) on true where contype='f' + ${props.schemaFilter ? `and connamespace::regnamespace::text ${props.schemaFilter}` : ''} ), views as ( select @@ -35,7 +39,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') + ${props.schemaFilter ? `and n.nspname ${props.schemaFilter}` : ''} ), transform_json as ( select @@ -71,48 +76,48 @@ 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":' ), ' :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 +144,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 ${props.schemaFilter ? `view_schema ${props.schemaFilter}` : 'true'} union all select view.view_id, @@ -189,3 +194,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 4f9cac03..03b407d4 100644 --- a/src/server/templates/typescript.ts +++ b/src/server/templates/typescript.ts @@ -32,7 +32,9 @@ export const apply = async ({ 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 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() +}) 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", },