From 5d013cb2f6669f1cee00244b9cfa14490bb636ac Mon Sep 17 00:00:00 2001 From: Charis <26616127+charislam@users.noreply.github.com> Date: Thu, 9 Apr 2026 14:32:02 -0400 Subject: [PATCH] refactor(pg-meta): expand safeSql usage (#44674) ## Summary by CodeRabbit * **Refactor** * Standardized SQL construction across the pg-meta package to use parameter-safe SQL fragments instead of raw string assembly, improving safety for dynamic values (filters, limits, offsets, identifiers) and unifying how exported SQL constants and query helpers are produced. No functional query behavior changes expected. --- packages/pg-meta/src/pg-meta-columns.ts | 30 +++-- packages/pg-meta/src/pg-meta-config.ts | 7 +- packages/pg-meta/src/pg-meta-extensions.ts | 45 ++++---- packages/pg-meta/src/pg-meta-functions.ts | 39 +++---- packages/pg-meta/src/pg-meta-indexes.ts | 19 ++-- packages/pg-meta/src/pg-meta-policies.ts | 28 +++-- packages/pg-meta/src/pg-meta-publications.ts | 70 ++++++------ packages/pg-meta/src/pg-meta-roles.ts | 103 +++++++++--------- packages/pg-meta/src/pg-meta-schemas.ts | 53 ++++----- .../pg-meta/src/pg-meta-table-privileges.ts | 68 +++++++----- packages/pg-meta/src/pg-meta-triggers.ts | 46 ++++---- packages/pg-meta/src/pg-meta-types.ts | 11 +- packages/pg-meta/src/pg-meta-version.ts | 3 +- packages/pg-meta/src/sql/config.ts | 4 +- packages/pg-meta/src/sql/extensions.ts | 4 +- packages/pg-meta/src/sql/functions.ts | 4 +- packages/pg-meta/src/sql/indexes.ts | 4 +- packages/pg-meta/src/sql/policies.ts | 4 +- packages/pg-meta/src/sql/publications.ts | 4 +- packages/pg-meta/src/sql/roles.ts | 4 +- packages/pg-meta/src/sql/schemas.ts | 4 +- packages/pg-meta/src/sql/table-privileges.ts | 4 +- packages/pg-meta/src/sql/triggers.ts | 4 +- packages/pg-meta/src/sql/types.ts | 4 +- packages/pg-meta/src/sql/version.ts | 4 +- 25 files changed, 310 insertions(+), 260 deletions(-) diff --git a/packages/pg-meta/src/pg-meta-columns.ts b/packages/pg-meta/src/pg-meta-columns.ts index 34d43ed4c2a..c99c5fc3279 100644 --- a/packages/pg-meta/src/pg-meta-columns.ts +++ b/packages/pg-meta/src/pg-meta-columns.ts @@ -2,7 +2,7 @@ import { z } from 'zod' import { DEFAULT_SYSTEM_SCHEMAS } from './constants' import { filterByList } from './helpers' -import { ident, literal } from './pg-format' +import { ident, literal, safeSql, type SafeSqlFragment } from './pg-format' import { COLUMNS_SQL } from './sql/columns' const pgColumnZod = z.object({ @@ -46,10 +46,10 @@ function list({ limit?: number offset?: number } = {}): { - sql: string + sql: SafeSqlFragment zod: typeof pgColumnArrayZod } { - let sql = ` + let sql = safeSql` with columns as (${COLUMNS_SQL}) select @@ -67,16 +67,16 @@ where ) if (filter) { - sql += ` and schema ${filter}` + sql = safeSql`${sql} and schema ${filter}` } if (tableId !== undefined) { - sql += ` and table_id = ${literal(tableId)} ` + sql = safeSql`${sql} and table_id = ${literal(tableId)} ` } if (limit) { - sql = `${sql} limit ${limit}` + sql = safeSql`${sql} limit ${literal(limit)}` } if (offset) { - sql = `${sql} offset ${offset}` + sql = safeSql`${sql} offset ${literal(offset)}` } return { sql, @@ -86,20 +86,20 @@ where type ColumnIdentifier = Pick | Pick -function getIdentifierWhereClause(identifier: ColumnIdentifier) { +function getIdentifierWhereClause(identifier: ColumnIdentifier): SafeSqlFragment { if ('id' in identifier && identifier.id) { - return `${ident('id')} = ${literal(identifier.id)}` + return safeSql`${ident('id')} = ${literal(identifier.id)}` } else if ('name' in identifier && identifier.name && identifier.schema && identifier.table) { - return `schema = ${literal(identifier.schema)} AND ${ident('table')} = ${literal(identifier.table)} AND name = ${literal(identifier.name)}` + return safeSql`schema = ${literal(identifier.schema)} AND ${ident('table')} = ${literal(identifier.table)} AND name = ${literal(identifier.name)}` } throw new Error('Must provide either id or schema, name and table') } function retrieve(identifier: ColumnIdentifier): { - sql: string + sql: SafeSqlFragment zod: typeof pgColumnOptionalZod } { - const sql = `WITH columns AS (${COLUMNS_SQL}) SELECT * FROM columns WHERE ${getIdentifierWhereClause(identifier)};` + const sql = safeSql`WITH columns AS (${COLUMNS_SQL}) SELECT * FROM columns WHERE ${getIdentifierWhereClause(identifier)};` return { sql, zod: pgColumnOptionalZod, @@ -393,10 +393,8 @@ COMMIT;` function remove( column: Pick, { cascade = false } = {} -): { sql: string } { - const sql = `ALTER TABLE ${ident(column.schema)}.${ident(column.table)} DROP COLUMN ${ident( - column.name - )} ${cascade ? 'CASCADE' : 'RESTRICT'};` +): { sql: SafeSqlFragment } { + const sql = safeSql`ALTER TABLE ${ident(column.schema)}.${ident(column.table)} DROP COLUMN ${ident(column.name)} ${cascade ? safeSql`CASCADE` : safeSql`RESTRICT`};` return { sql } } diff --git a/packages/pg-meta/src/pg-meta-config.ts b/packages/pg-meta/src/pg-meta-config.ts index 3a688c31e1c..867b3efbb79 100644 --- a/packages/pg-meta/src/pg-meta-config.ts +++ b/packages/pg-meta/src/pg-meta-config.ts @@ -1,5 +1,6 @@ import { z } from 'zod' +import { literal, safeSql, type SafeSqlFragment } from './pg-format' import { CONFIG_SQL } from './sql/config' const pgConfigZod = z.object({ @@ -33,15 +34,15 @@ function list({ limit?: number offset?: number } = {}): { - sql: string + sql: SafeSqlFragment zod: typeof pgConfigArrayZod } { let sql = CONFIG_SQL if (limit) { - sql += ` LIMIT ${limit}` + sql = safeSql`${sql} LIMIT ${literal(limit)}` } if (offset) { - sql += ` OFFSET ${offset}` + sql = safeSql`${sql} OFFSET ${literal(offset)}` } return { sql, diff --git a/packages/pg-meta/src/pg-meta-extensions.ts b/packages/pg-meta/src/pg-meta-extensions.ts index 096bf257af7..c59778dbb27 100644 --- a/packages/pg-meta/src/pg-meta-extensions.ts +++ b/packages/pg-meta/src/pg-meta-extensions.ts @@ -1,6 +1,6 @@ import { z } from 'zod' -import { ident, literal } from './pg-format' +import { ident, literal, safeSql, type SafeSqlFragment } from './pg-format' import { EXTENSIONS_SQL } from './sql/extensions' const pgExtensionZod = z.object({ @@ -23,15 +23,15 @@ function list({ limit?: number offset?: number } = {}): { - sql: string + sql: SafeSqlFragment zod: typeof pgExtensionArrayZod } { let sql = EXTENSIONS_SQL if (limit) { - sql = `${sql} LIMIT ${limit}` + sql = safeSql`${sql} LIMIT ${literal(limit)}` } if (offset) { - sql = `${sql} OFFSET ${offset}` + sql = safeSql`${sql} OFFSET ${literal(offset)}` } return { sql, @@ -40,10 +40,10 @@ function list({ } function retrieve({ name }: { name: string }): { - sql: string + sql: SafeSqlFragment zod: typeof pgExtensionOptionalZod } { - const sql = `${EXTENSIONS_SQL} WHERE name = ${literal(name)};` + const sql = safeSql`${EXTENSIONS_SQL} WHERE name = ${literal(name)};` return { sql, zod: pgExtensionOptionalZod, @@ -58,13 +58,13 @@ type ExtensionCreateParams = { } function create({ name, schema, version, cascade = false }: ExtensionCreateParams): { - sql: string + sql: SafeSqlFragment } { - const sql = ` + const sql = safeSql` CREATE EXTENSION ${ident(name)} - ${schema === undefined ? '' : `SCHEMA ${ident(schema)}`} - ${version === undefined ? '' : `VERSION ${literal(version)}`} - ${cascade ? 'CASCADE' : ''};` + ${schema === undefined ? safeSql`` : safeSql`SCHEMA ${ident(schema)}`} + ${version === undefined ? safeSql`` : safeSql`VERSION ${literal(version)}`} + ${cascade ? safeSql`CASCADE` : safeSql``};` return { sql } } @@ -77,17 +77,19 @@ type ExtensionUpdateParams = { function update( name: string, { update = false, version, schema }: ExtensionUpdateParams -): { sql: string } { - let updateSql = '' +): { sql: SafeSqlFragment } { + let updateSql = safeSql`` if (update) { - updateSql = `ALTER EXTENSION ${ident(name)} UPDATE ${ - version === undefined ? '' : `TO ${literal(version)}` + updateSql = safeSql`ALTER EXTENSION ${ident(name)} UPDATE ${ + version === undefined ? safeSql`` : safeSql`TO ${literal(version)}` };` } const schemaSql = - schema === undefined ? '' : `ALTER EXTENSION ${ident(name)} SET SCHEMA ${ident(schema)};` + schema === undefined + ? safeSql`` + : safeSql`ALTER EXTENSION ${ident(name)} SET SCHEMA ${ident(schema)};` - const sql = `BEGIN; ${updateSql} ${schemaSql} COMMIT;` + const sql = safeSql`BEGIN; ${updateSql} ${schemaSql} COMMIT;` return { sql } } @@ -95,8 +97,13 @@ type ExtensionRemoveParams = { cascade?: boolean } -function remove(name: string, { cascade = false }: ExtensionRemoveParams = {}): { sql: string } { - const sql = `DROP EXTENSION ${ident(name)} ${cascade ? 'CASCADE' : 'RESTRICT'};` +function remove( + name: string, + { cascade = false }: ExtensionRemoveParams = {} +): { + sql: SafeSqlFragment +} { + const sql = safeSql`DROP EXTENSION ${ident(name)} ${cascade ? safeSql`CASCADE` : safeSql`RESTRICT`};` return { sql } } diff --git a/packages/pg-meta/src/pg-meta-functions.ts b/packages/pg-meta/src/pg-meta-functions.ts index e9c6d09c1c0..886702ccd75 100644 --- a/packages/pg-meta/src/pg-meta-functions.ts +++ b/packages/pg-meta/src/pg-meta-functions.ts @@ -2,7 +2,7 @@ import { z } from 'zod' import { DEFAULT_SYSTEM_SCHEMAS } from './constants' import { filterByList } from './helpers' -import { ident, literal } from './pg-format' +import { ident, joinSqlFragments, literal, safeSql, type SafeSqlFragment } from './pg-format' import { FUNCTIONS_SQL } from './sql/functions' export const pgFunctionZod = z.object({ @@ -55,10 +55,10 @@ export function list({ limit?: number offset?: number } = {}): { - sql: string + sql: SafeSqlFragment zod: typeof pgFunctionArrayZod } { - let sql = /* SQL */ ` + let sql = safeSql` with f as ( ${FUNCTIONS_SQL} ) @@ -72,13 +72,13 @@ export function list({ !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined ) if (filter) { - sql += ` where schema ${filter}` + sql = safeSql`${sql} where schema ${filter}` } if (limit) { - sql = `${sql} limit ${limit}` + sql = safeSql`${sql} limit ${literal(limit)}` } if (offset) { - sql = `${sql} offset ${offset}` + sql = safeSql`${sql} offset ${literal(offset)}` } return { @@ -88,7 +88,7 @@ export function list({ } type FunctionsRetrieveReturn = { - sql: string + sql: SafeSqlFragment zod: typeof pgFunctionOptionalZod } @@ -114,7 +114,7 @@ export function retrieve({ args?: string[] }): FunctionsRetrieveReturn { if (id) { - const sql = /* SQL */ ` + const sql = safeSql` with f as ( ${FUNCTIONS_SQL} ) @@ -127,16 +127,8 @@ export function retrieve({ zod: pgFunctionOptionalZod, } } else if (name && schema && args) { - const sql = /* SQL */ `with f as ( - ${FUNCTIONS_SQL} - ) - select - f.* - from f join pg_proc as p on id = p.oid where schema = ${literal( - schema - )} and name = ${literal(name)} and p.proargtypes::text = ${ - args.length - ? /* SQL */ `( + const argsFragment = args.length + ? safeSql`( select string_agg(type_oid::text, ' ') from ( select ( split_args.arr[ @@ -148,15 +140,20 @@ export function retrieve({ ) as type_oid from ( select string_to_array( unnest( - array[${args.map(literal)}] + array[${joinSqlFragments(args.map(literal), ',')}] ), ' ' ) as arr ) as split_args ) args )` - : literal('') - }` + : literal('') + const sql = safeSql`with f as ( + ${FUNCTIONS_SQL} + ) + select + f.* + from f join pg_proc as p on id = p.oid where schema = ${literal(schema)} and name = ${literal(name)} and p.proargtypes::text = ${argsFragment}` return { sql, diff --git a/packages/pg-meta/src/pg-meta-indexes.ts b/packages/pg-meta/src/pg-meta-indexes.ts index 3fd4f12487a..959f55de90f 100644 --- a/packages/pg-meta/src/pg-meta-indexes.ts +++ b/packages/pg-meta/src/pg-meta-indexes.ts @@ -2,7 +2,7 @@ import { z } from 'zod' import { DEFAULT_SYSTEM_SCHEMAS } from './constants' import { filterByList } from './helpers' -import { literal } from './pg-format' +import { literal, safeSql, type SafeSqlFragment } from './pg-format' import { INDEXES_SQL } from './sql/indexes' const pgIndexZod = z.object({ @@ -54,10 +54,10 @@ function list({ limit?: number offset?: number } = {}): { - sql: string + sql: SafeSqlFragment zod: typeof pgIndexArrayZod } { - let sql = ` + let sql = safeSql` with indexes as (${INDEXES_SQL}) select * from indexes @@ -68,13 +68,13 @@ function list({ !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined ) if (filter) { - sql += ` where schema ${filter}` + sql = safeSql`${sql} where schema ${filter}` } if (limit) { - sql += ` limit ${limit}` + sql = safeSql`${sql} limit ${literal(limit)}` } if (offset) { - sql += ` offset ${offset}` + sql = safeSql`${sql} offset ${literal(offset)}` } return { sql, @@ -82,8 +82,11 @@ function list({ } } -function retrieve({ id }: { id: number }): { sql: string; zod: typeof pgIndexOptionalZod } { - const sql = ` +function retrieve({ id }: { id: number }): { + sql: SafeSqlFragment + zod: typeof pgIndexOptionalZod +} { + const sql = safeSql` with indexes as (${INDEXES_SQL}) select * from indexes diff --git a/packages/pg-meta/src/pg-meta-policies.ts b/packages/pg-meta/src/pg-meta-policies.ts index d8a6e00112e..cf82713820e 100644 --- a/packages/pg-meta/src/pg-meta-policies.ts +++ b/packages/pg-meta/src/pg-meta-policies.ts @@ -2,7 +2,7 @@ import { z } from 'zod' import { DEFAULT_SYSTEM_SCHEMAS } from './constants' import { filterByList } from './helpers' -import { ident, literal } from './pg-format' +import { ident, literal, safeSql, type SafeSqlFragment } from './pg-format' import { POLICIES_SQL } from './sql/policies' const pgPolicyZod = z.object({ @@ -30,11 +30,11 @@ export type PGPolicy = z.infer type PolicyIdentifier = Pick | Pick -function getIdentifierWhereClause(identifier: PolicyIdentifier): string { +function getIdentifierWhereClause(identifier: PolicyIdentifier): SafeSqlFragment { if ('id' in identifier && identifier.id) { - return `id = ${literal(identifier.id)}` + return safeSql`id = ${literal(identifier.id)}` } else if ('name' in identifier && identifier.name && identifier.schema && identifier.table) { - return `name = ${literal(identifier.name)} AND schema = ${literal(identifier.schema)} AND table = ${literal(identifier.table)}` + return safeSql`name = ${literal(identifier.name)} AND schema = ${literal(identifier.schema)} AND table = ${literal(identifier.table)}` } throw new Error('Must provide either id or name, schema and table') } @@ -52,10 +52,10 @@ function list({ limit?: number offset?: number } = {}): { - sql: string + sql: SafeSqlFragment zod: typeof pgPolicyArrayZod } { - let sql = ` + let sql = safeSql` with policies as (${POLICIES_SQL}) select * from policies @@ -66,13 +66,13 @@ function list({ !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined ) if (filter) { - sql += `where schema ${filter}` + sql = safeSql`${sql}where schema ${filter}` } if (limit) { - sql += ` limit ${limit}` + sql = safeSql`${sql} limit ${literal(limit)}` } if (offset) { - sql += ` offset ${offset}` + sql = safeSql`${sql} offset ${literal(offset)}` } return { sql, @@ -81,10 +81,10 @@ function list({ } function retrieve(identifier: PolicyIdentifier): { - sql: string + sql: SafeSqlFragment zod: typeof pgPolicyOptionalZod } { - const sql = `with policies as (${POLICIES_SQL}) select * from policies where ${getIdentifierWhereClause(identifier)};` + const sql = safeSql`with policies as (${POLICIES_SQL}) select * from policies where ${getIdentifierWhereClause(identifier)};` return { sql, zod: pgPolicyOptionalZod, @@ -147,10 +147,8 @@ function update( return { sql } } -function remove(identifier: Pick): { sql: string } { - const sql = `DROP POLICY ${ident(identifier.name)} ON ${ident(identifier.schema)}.${ident( - identifier.table - )};` +function remove(identifier: Pick): { sql: SafeSqlFragment } { + const sql = safeSql`DROP POLICY ${ident(identifier.name)} ON ${ident(identifier.schema)}.${ident(identifier.table)};` return { sql } } diff --git a/packages/pg-meta/src/pg-meta-publications.ts b/packages/pg-meta/src/pg-meta-publications.ts index bb61a896082..eecd5d1c41e 100644 --- a/packages/pg-meta/src/pg-meta-publications.ts +++ b/packages/pg-meta/src/pg-meta-publications.ts @@ -1,6 +1,6 @@ import { z } from 'zod' -import { ident, literal } from './pg-format' +import { ident, joinSqlFragments, literal, safeSql, type SafeSqlFragment } from './pg-format' import { PUBLICATIONS_SQL } from './sql/publications' const pgPublicationTableZod = z.object({ @@ -32,15 +32,15 @@ function list({ limit?: number offset?: number } = {}): { - sql: string + sql: SafeSqlFragment zod: typeof pgPublicationArrayZod } { - let sql = `with publications as (${PUBLICATIONS_SQL}) select * from publications` + let sql = safeSql`with publications as (${PUBLICATIONS_SQL}) select * from publications` if (limit) { - sql += ` limit ${limit}` + sql = safeSql`${sql} limit ${literal(limit)}` } if (offset) { - sql += ` offset ${offset}` + sql = safeSql`${sql} offset ${literal(offset)}` } return { sql, @@ -50,20 +50,20 @@ function list({ type PublicationIdentifier = Pick | Pick -function getIdentifierWhereClause(identifier: PublicationIdentifier) { +function getIdentifierWhereClause(identifier: PublicationIdentifier): SafeSqlFragment { if ('id' in identifier && identifier.id) { - return `${ident('id')} = ${literal(identifier.id)}` + return safeSql`${ident('id')} = ${literal(identifier.id)}` } else if ('name' in identifier && identifier.name) { - return `${ident('name')} = ${literal(identifier.name)}` + return safeSql`${ident('name')} = ${literal(identifier.name)}` } throw new Error('Must provide either id or name') } function retrieve(identifier: PublicationIdentifier): { - sql: string + sql: SafeSqlFragment zod: typeof pgPublicationOptionalZod } { - const sql = `with publications as (${PUBLICATIONS_SQL}) select * from publications where ${getIdentifierWhereClause(identifier)};` + const sql = safeSql`with publications as (${PUBLICATIONS_SQL}) select * from publications where ${getIdentifierWhereClause(identifier)};` return { sql, zod: pgPublicationOptionalZod, @@ -86,35 +86,35 @@ function create({ publish_delete = false, publish_truncate = false, tables = null, -}: PublicationCreateParams): { sql: string } { - let tableClause: string +}: PublicationCreateParams): { sql: SafeSqlFragment } { + let tableClause: SafeSqlFragment if (tables === undefined || tables === null) { - tableClause = 'FOR ALL TABLES' + tableClause = safeSql`FOR ALL TABLES` } else if (tables.length === 0) { - tableClause = '' + tableClause = safeSql`` } else { - tableClause = `FOR TABLE ${tables - .map((t) => { + tableClause = safeSql`FOR TABLE ${joinSqlFragments( + tables.map((t) => { if (!t.includes('.')) { return ident(t) } - const [schema, ...rest] = t.split('.') const table = rest.join('.') - return `${ident(schema)}.${ident(table)}` - }) - .join(',')}` + return safeSql`${ident(schema)}.${ident(table)}` + }), + ',' + )}` } - let publishOps = [] + const publishOps: Array = [] if (publish_insert) publishOps.push('insert') if (publish_update) publishOps.push('update') if (publish_delete) publishOps.push('delete') if (publish_truncate) publishOps.push('truncate') - const sql = ` + const sql = safeSql` CREATE PUBLICATION ${ident(name)} ${tableClause} - WITH (publish = '${publishOps.join(',')}');` + WITH (publish = ${literal(publishOps.join(','))});` return { sql } } @@ -140,21 +140,21 @@ function update( publish_truncate, tables, }: PublicationUpdateParams -): { sql: string } { - const sql = ` +): { sql: SafeSqlFragment } { + const sql = safeSql` do $$ declare id oid := ${literal(id)}; old record; - new_name text := ${name === undefined ? null : literal(name)}; - new_owner text := ${owner === undefined ? null : literal(owner)}; - new_publish_insert bool := ${publish_insert ?? null}; - new_publish_update bool := ${publish_update ?? null}; - new_publish_delete bool := ${publish_delete ?? null}; - new_publish_truncate bool := ${publish_truncate ?? null}; + new_name text := ${name === undefined ? literal(null) : literal(name)}; + new_owner text := ${owner === undefined ? literal(null) : literal(owner)}; + new_publish_insert bool := ${literal(publish_insert ?? null)}; + new_publish_update bool := ${literal(publish_update ?? null)}; + new_publish_delete bool := ${literal(publish_delete ?? null)}; + new_publish_truncate bool := ${literal(publish_truncate ?? null)}; new_tables text := ${ tables === undefined - ? null + ? literal(null) : literal( tables === null ? 'all tables' @@ -166,7 +166,7 @@ declare const [schema, ...rest] = t.split('.') const table = rest.join('.') - return `${ident(schema)}.${ident(table)}` + return safeSql`${ident(schema)}.${ident(table)}` }) .join(',') ) @@ -237,8 +237,8 @@ end $$; return { sql } } -function remove(publication: Pick): { sql: string } { - const sql = `DROP PUBLICATION IF EXISTS ${ident(publication.name)};` +function remove(publication: Pick): { sql: SafeSqlFragment } { + const sql = safeSql`DROP PUBLICATION IF EXISTS ${ident(publication.name)};` return { sql } } diff --git a/packages/pg-meta/src/pg-meta-roles.ts b/packages/pg-meta/src/pg-meta-roles.ts index 5d4a7edcf5b..ab2bf21ecde 100644 --- a/packages/pg-meta/src/pg-meta-roles.ts +++ b/packages/pg-meta/src/pg-meta-roles.ts @@ -1,6 +1,6 @@ import { z } from 'zod' -import { ident, literal } from './pg-format' +import { ident, joinSqlFragments, literal, safeSql, type SafeSqlFragment } from './pg-format' import { ROLES_SQL } from './sql/roles' const pgRoleZod = z.object({ @@ -32,10 +32,10 @@ function list({ limit?: number offset?: number } = {}): { - sql: string + sql: SafeSqlFragment zod: typeof pgRoleArrayZod } { - let sql = ` + let sql = safeSql` with roles as (${ROLES_SQL}) select @@ -54,13 +54,13 @@ where // ERROR: role name "pg_myrole" is reserved // DETAIL: Role names starting with "pg_" are reserved. // ``` - sql += ` and not pg_catalog.starts_with(name, 'pg_')` + sql = safeSql`${sql} and not pg_catalog.starts_with(name, 'pg_')` } if (limit) { - sql += ` limit ${limit}` + sql = safeSql`${sql} limit ${literal(limit)}` } if (offset) { - sql += ` offset ${offset}` + sql = safeSql`${sql} offset ${literal(offset)}` } return { sql, @@ -70,20 +70,20 @@ where type RoleIdentifier = Pick | Pick -function getIdentifierWhereClause(identifier: RoleIdentifier) { +function getIdentifierWhereClause(identifier: RoleIdentifier): SafeSqlFragment { if ('id' in identifier && identifier.id) { - return `${ident('id')} = ${literal(identifier.id)}` + return safeSql`${ident('id')} = ${literal(identifier.id)}` } else if ('name' in identifier && identifier.name) { - return `${ident('name')} = ${literal(identifier.name)}` + return safeSql`${ident('name')} = ${literal(identifier.name)}` } throw new Error('Must provide either id or name') } function retrieve(identifier: RoleIdentifier): { - sql: string + sql: SafeSqlFragment zod: typeof pgRoleOptionalZod } { - const sql = `with roles as (${ROLES_SQL}) select * from roles where ${getIdentifierWhereClause(identifier)};` + const sql = safeSql`with roles as (${ROLES_SQL}) select * from roles where ${getIdentifierWhereClause(identifier)};` return { sql, zod: pgRoleOptionalZod, @@ -102,9 +102,9 @@ type RoleCreateParams = { connectionLimit?: number password?: string validUntil?: string - memberOf?: string[] - members?: string[] - admins?: string[] + memberOf?: Array + members?: Array + admins?: Array config?: Record } function create({ @@ -123,26 +123,29 @@ function create({ members = [], admins = [], config = {}, -}: RoleCreateParams): { sql: string } { - const sql = ` +}: RoleCreateParams): { sql: SafeSqlFragment } { + const sql = safeSql` create role ${ident(name)} - ${isSuperuser ? 'superuser' : ''} - ${canCreateDb ? 'createdb' : ''} - ${canCreateRole ? 'createrole' : ''} - ${inheritRole ? '' : 'noinherit'} - ${canLogin ? 'login' : ''} - ${isReplicationRole ? 'replication' : ''} - ${canBypassRls ? 'bypassrls' : ''} - connection limit ${connectionLimit} - ${password === undefined ? '' : `password ${literal(password)}`} - ${validUntil === undefined ? '' : `valid until ${literal(validUntil)}`} - ${memberOf.length === 0 ? '' : `in role ${memberOf.map(ident).join(',')}`} - ${members.length === 0 ? '' : `role ${members.map(ident).join(',')}`} - ${admins.length === 0 ? '' : `admin ${admins.map(ident).join(',')}`} + ${isSuperuser ? safeSql`superuser` : safeSql``} + ${canCreateDb ? safeSql`createdb` : safeSql``} + ${canCreateRole ? safeSql`createrole` : safeSql``} + ${inheritRole ? safeSql`` : safeSql`noinherit`} + ${canLogin ? safeSql`login` : safeSql``} + ${isReplicationRole ? safeSql`replication` : safeSql``} + ${canBypassRls ? safeSql`bypassrls` : safeSql``} + connection limit ${literal(connectionLimit)} + ${password === undefined ? safeSql`` : safeSql`password ${literal(password)}`} + ${validUntil === undefined ? safeSql`` : safeSql`valid until ${literal(validUntil)}`} + ${memberOf.length === 0 ? safeSql`` : safeSql`in role ${joinSqlFragments(memberOf.map(ident), ',')}`} + ${members.length === 0 ? safeSql`` : safeSql`role ${joinSqlFragments(members.map(ident), ',')}`} + ${admins.length === 0 ? safeSql`` : safeSql`admin ${joinSqlFragments(admins.map(ident), ',')}`} ; -${Object.entries(config) - .map(([param, value]) => `alter role ${ident(name)} set ${ident(param)} = ${literal(value)};`) - .join('\n')} +${joinSqlFragments( + Object.entries(config).map( + ([param, value]) => safeSql`alter role ${ident(name)} set ${ident(param)} = ${literal(value)};` + ), + '\n' +)} ` return { sql } } @@ -160,7 +163,7 @@ type RoleUpdateParams = { password?: string validUntil?: string } -function update(identifier: RoleIdentifier, params: RoleUpdateParams): { sql: string } { +function update(identifier: RoleIdentifier, params: RoleUpdateParams): { sql: SafeSqlFragment } { const { name: newName, isSuperuser, @@ -174,7 +177,7 @@ function update(identifier: RoleIdentifier, params: RoleUpdateParams): { sql: st password, validUntil, } = params - const sql = ` + const sql = safeSql` do $$ declare old record; @@ -186,22 +189,22 @@ begin end if; execute(format('alter role %I - ${isSuperuser === undefined ? '' : isSuperuser ? 'superuser' : 'nosuperuser'} - ${canCreateDb === undefined ? '' : canCreateDb ? 'createdb' : 'nocreatedb'} - ${canCreateRole === undefined ? '' : canCreateRole ? 'createrole' : 'nocreaterole'} - ${inheritRole === undefined ? '' : inheritRole ? 'inherit' : 'noinherit'} - ${canLogin === undefined ? '' : canLogin ? 'login' : 'nologin'} - ${isReplicationRole === undefined ? '' : isReplicationRole ? 'replication' : 'noreplication'} - ${canBypassRls === undefined ? '' : canBypassRls ? 'bypassrls' : 'nobypassrls'} - ${connectionLimit === undefined ? '' : `connection limit ${connectionLimit}`} - ${password === undefined ? '' : `password ${literal(password)}`} - ${validUntil === undefined ? '' : `valid until %L`} - ', old.name${validUntil === undefined ? '' : `, ${literal(validUntil)}`})); + ${isSuperuser === undefined ? safeSql`` : isSuperuser ? safeSql`superuser` : safeSql`nosuperuser`} + ${canCreateDb === undefined ? safeSql`` : canCreateDb ? safeSql`createdb` : safeSql`nocreatedb`} + ${canCreateRole === undefined ? safeSql`` : canCreateRole ? safeSql`createrole` : safeSql`nocreaterole`} + ${inheritRole === undefined ? safeSql`` : inheritRole ? safeSql`inherit` : safeSql`noinherit`} + ${canLogin === undefined ? safeSql`` : canLogin ? safeSql`login` : safeSql`nologin`} + ${isReplicationRole === undefined ? safeSql`` : isReplicationRole ? safeSql`replication` : safeSql`noreplication`} + ${canBypassRls === undefined ? safeSql`` : canBypassRls ? safeSql`bypassrls` : safeSql`nobypassrls`} + ${connectionLimit === undefined ? safeSql`` : safeSql`connection limit ${literal(connectionLimit)}`} + ${password === undefined ? safeSql`` : safeSql`password ${literal(password)}`} + ${validUntil === undefined ? safeSql`` : safeSql`valid until %L`} + ', old.name${validUntil === undefined ? safeSql`` : safeSql`, ${literal(validUntil)}`})); ${ newName === undefined - ? '' - : ` + ? safeSql`` + : safeSql` -- Using the same name in the rename clause gives an error, so only do it if the new name is different. if ${literal(newName)} != old.name then execute(format('alter role %I rename to %I;', old.name, ${literal(newName)})); @@ -220,8 +223,8 @@ type RoleRemoveParams = { function remove( identifier: RoleIdentifier, { ifExists = false }: RoleRemoveParams = {} -): { sql: string } { - const sql = ` +): { sql: SafeSqlFragment } { + const sql = safeSql` do $$ declare old record; @@ -232,7 +235,7 @@ begin raise exception 'Cannot find role with id %', id; end if; - execute(format('drop role ${ifExists ? 'if exists' : ''} %I;', old.name)); + execute(format('drop role ${ifExists ? safeSql`if exists` : safeSql``} %I;', old.name)); end $$; ` diff --git a/packages/pg-meta/src/pg-meta-schemas.ts b/packages/pg-meta/src/pg-meta-schemas.ts index 17cf6ea3631..3ff8ba8d487 100644 --- a/packages/pg-meta/src/pg-meta-schemas.ts +++ b/packages/pg-meta/src/pg-meta-schemas.ts @@ -1,7 +1,7 @@ import { z } from 'zod' import { DEFAULT_SYSTEM_SCHEMAS } from './constants' -import { ident, literal } from './pg-format' +import { ident, joinSqlFragments, literal, safeSql, type SafeSqlFragment } from './pg-format' import { SCHEMAS_SQL } from './sql/schemas' const pgSchemaZod = z.object({ @@ -22,18 +22,18 @@ function list({ limit?: number offset?: number } = {}): { - sql: string + sql: SafeSqlFragment zod: typeof pgSchemaArrayZod } { let sql = SCHEMAS_SQL if (!includeSystemSchemas) { - sql = `${sql} and not (n.nspname in (${DEFAULT_SYSTEM_SCHEMAS.map(literal).join(',')}))` + sql = safeSql`${sql} and not (n.nspname in (${joinSqlFragments(DEFAULT_SYSTEM_SCHEMAS.map(literal), ',')}))` } if (limit) { - sql = `${sql} limit ${limit}` + sql = safeSql`${sql} limit ${literal(limit)}` } if (offset) { - sql = `${sql} offset ${offset}` + sql = safeSql`${sql} offset ${literal(offset)}` } return { sql, @@ -41,20 +41,23 @@ function list({ } } -function retrieve({ id }: { id: number }): { sql: string; zod: typeof pgSchemaOptionalZod } -function retrieve({ name }: { name: string }): { sql: string; zod: typeof pgSchemaOptionalZod } +function retrieve({ id }: { id: number }): { sql: SafeSqlFragment; zod: typeof pgSchemaOptionalZod } +function retrieve({ name }: { name: string }): { + sql: SafeSqlFragment + zod: typeof pgSchemaOptionalZod +} function retrieve({ id, name }: { id?: number; name?: string }): { - sql: string + sql: SafeSqlFragment zod: typeof pgSchemaOptionalZod } { if (id) { - const sql = `${SCHEMAS_SQL} and n.oid = ${literal(id)};` + const sql = safeSql`${SCHEMAS_SQL} and n.oid = ${literal(id)};` return { sql, zod: pgSchemaOptionalZod, } } else { - const sql = `${SCHEMAS_SQL} and n.nspname = ${literal(name)};` + const sql = safeSql`${SCHEMAS_SQL} and n.nspname = ${literal(name)};` return { sql, zod: pgSchemaOptionalZod, @@ -66,9 +69,9 @@ type SchemaCreateParams = { name: string owner?: string } -function create({ name, owner }: SchemaCreateParams): { sql: string } { - const sql = `create schema ${ident(name)} - ${owner === undefined ? '' : `authorization ${ident(owner)}`}; +function create({ name, owner }: SchemaCreateParams): { sql: SafeSqlFragment } { + const sql = safeSql`create schema ${ident(name)} + ${owner === undefined ? safeSql`` : safeSql`authorization ${ident(owner)}`}; ` return { sql } } @@ -77,8 +80,8 @@ type SchemaUpdateParams = { name?: string owner?: string } -function update({ id }: { id: number }, params: SchemaUpdateParams): { sql: string } -function update({ name }: { name: string }, params: SchemaUpdateParams): { sql: string } +function update({ id }: { id: number }, params: SchemaUpdateParams): { sql: SafeSqlFragment } +function update({ name }: { name: string }, params: SchemaUpdateParams): { sql: SafeSqlFragment } function update( { id, @@ -88,14 +91,14 @@ function update( name?: string }, { name: newName, owner }: SchemaUpdateParams -): { sql: string } { - const sql = ` +): { sql: SafeSqlFragment } { + const sql = safeSql` do $$ declare - id oid := ${id === undefined ? `${literal(name)}::regnamespace` : literal(id)}; + id oid := ${id === undefined ? safeSql`${literal(name)}::regnamespace` : literal(id)}; old record; - new_name text := ${newName === undefined ? null : literal(newName)}; - new_owner text := ${owner === undefined ? null : literal(owner)}; + new_name text := ${newName === undefined ? literal(null) : literal(newName)}; + new_owner text := ${owner === undefined ? literal(null) : literal(owner)}; begin select * into old from pg_namespace where oid = id; if old is null then @@ -119,8 +122,8 @@ $$; type SchemaRemoveParams = { cascade?: boolean } -function remove({ id }: { id: number }, params?: SchemaRemoveParams): { sql: string } -function remove({ name }: { name: string }, params?: SchemaRemoveParams): { sql: string } +function remove({ id }: { id: number }, params?: SchemaRemoveParams): { sql: SafeSqlFragment } +function remove({ name }: { name: string }, params?: SchemaRemoveParams): { sql: SafeSqlFragment } function remove( { id, @@ -130,11 +133,11 @@ function remove( name?: string }, { cascade = false }: SchemaRemoveParams = {} -): { sql: string } { - const sql = ` +): { sql: SafeSqlFragment } { + const sql = safeSql` do $$ declare - id oid := ${id === undefined ? `${literal(name)}::regnamespace` : literal(id)}; + id oid := ${id === undefined ? safeSql`${literal(name)}::regnamespace` : literal(id)}; old record; cascade bool := ${literal(cascade)}; begin diff --git a/packages/pg-meta/src/pg-meta-table-privileges.ts b/packages/pg-meta/src/pg-meta-table-privileges.ts index d72afa57c96..984a1b9b22b 100644 --- a/packages/pg-meta/src/pg-meta-table-privileges.ts +++ b/packages/pg-meta/src/pg-meta-table-privileges.ts @@ -2,7 +2,14 @@ import { z } from 'zod' import { DEFAULT_SYSTEM_SCHEMAS } from './constants' import { filterByList } from './helpers' -import { ident, literal } from './pg-format' +import { + ident, + joinSqlFragments, + keyword, + literal, + safeSql, + type SafeSqlFragment, +} from './pg-format' import { TABLE_PRIVILEGES_SQL } from './sql/table-privileges' const pgTablePrivilegesZod = z.object({ @@ -50,10 +57,10 @@ function list({ limit?: number offset?: number } = {}): { - sql: string + sql: SafeSqlFragment zod: typeof pgTablePrivilegesArrayZod } { - let sql = ` + let sql = safeSql` with table_privileges as (${TABLE_PRIVILEGES_SQL}) select * from table_privileges @@ -64,13 +71,13 @@ from table_privileges !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined ) if (filter) { - sql += ` where schema ${filter}` + sql = safeSql`${sql} where schema ${filter}` } if (limit) { - sql += ` limit ${limit}` + sql = safeSql`${sql} limit ${literal(limit)}` } if (offset) { - sql += ` offset ${offset}` + sql = safeSql`${sql} offset ${literal(offset)}` } return { sql, @@ -78,9 +85,12 @@ from table_privileges } } -function retrieve({ id }: { id: number }): { sql: string; zod: typeof pgTablePrivilegesOptionalZod } +function retrieve({ id }: { id: number }): { + sql: SafeSqlFragment + zod: typeof pgTablePrivilegesOptionalZod +} function retrieve({ name, schema }: { name: string; schema?: string }): { - sql: string + sql: SafeSqlFragment zod: typeof pgTablePrivilegesOptionalZod } function retrieve({ @@ -92,11 +102,11 @@ function retrieve({ name?: string schema?: string }): { - sql: string + sql: SafeSqlFragment zod: typeof pgTablePrivilegesOptionalZod } { if (id) { - const sql = /* SQL */ ` + const sql = /* SQL */ safeSql` with table_privileges as (${TABLE_PRIVILEGES_SQL}) select * from table_privileges @@ -106,7 +116,7 @@ where table_privileges.relation_id = ${literal(id)};` zod: pgTablePrivilegesOptionalZod, } } else { - const sql = /* SQL */ ` + const sql = /* SQL */ safeSql` with table_privileges as (${TABLE_PRIVILEGES_SQL}) select * from table_privileges @@ -135,18 +145,19 @@ type TablePrivilegesGrant = { | 'MAINTAIN' isGrantable?: boolean } -function grant(grants: TablePrivilegesGrant[]): { sql: string } { - const sql = ` +function grant(grants: TablePrivilegesGrant[]): { sql: SafeSqlFragment } { + const sql = safeSql` do $$ begin -${grants - .map( +${joinSqlFragments( + grants.map( ({ privilegeType, relationId, grantee, isGrantable }) => - `execute format('grant ${privilegeType} on table %s to ${ - grantee.toLowerCase() === 'public' ? 'public' : ident(grantee) - } ${isGrantable ? 'with grant option' : ''}', ${relationId}::regclass);` - ) - .join('\n')} + safeSql`execute format('grant ${keyword(privilegeType)} on table %s to ${ + grantee.toLowerCase() === 'public' ? safeSql`public` : ident(grantee) + } ${isGrantable ? safeSql`with grant option` : safeSql``}', ${literal(relationId)}::regclass);` + ), + '\n' +)} end $$; ` return { sql } @@ -166,16 +177,19 @@ type TablePrivilegesRevoke = { | 'TRIGGER' | 'MAINTAIN' } -function revoke(revokes: TablePrivilegesRevoke[]): { sql: string } { - const sql = ` +function revoke(revokes: TablePrivilegesRevoke[]): { sql: SafeSqlFragment } { + const sql = safeSql` do $$ begin -${revokes - .map( +${joinSqlFragments( + revokes.map( ({ privilegeType, relationId, grantee }) => - `execute format('revoke ${privilegeType} on table %s from ${grantee.toLowerCase() === 'public' ? 'public' : ident(grantee)}', ${relationId}::regclass);` - ) - .join('\n')} + safeSql`execute format('revoke ${keyword(privilegeType)} on table %s from ${ + grantee.toLowerCase() === 'public' ? safeSql`public` : ident(grantee) + }', ${literal(relationId)}::regclass);` + ), + '\n' +)} end $$; ` return { sql } diff --git a/packages/pg-meta/src/pg-meta-triggers.ts b/packages/pg-meta/src/pg-meta-triggers.ts index 5bae66efa16..f2a10184334 100644 --- a/packages/pg-meta/src/pg-meta-triggers.ts +++ b/packages/pg-meta/src/pg-meta-triggers.ts @@ -2,17 +2,17 @@ import { z } from 'zod' import { DEFAULT_SYSTEM_SCHEMAS } from './constants' import { filterByList } from './helpers' -import { ident, literal } from './pg-format' +import { ident, keyword, literal, safeSql, type SafeSqlFragment } from './pg-format' import { TRIGGERS_SQL } from './sql/triggers' type TriggerIdentifier = Pick | Pick -function getIdentifierWhereClause(identifier: TriggerIdentifier): string { +function getIdentifierWhereClause(identifier: TriggerIdentifier): SafeSqlFragment { if ('id' in identifier && identifier.id) { - return `${ident('id')} = ${literal(identifier.id)}` + return safeSql`${ident('id')} = ${literal(identifier.id)}` } if ('name' in identifier && identifier.name && identifier.table && identifier.schema) { - return `${ident('name')} = ${literal(identifier.name)} and ${ident('schema')} = ${literal(identifier.schema)} and ${ident('table')} = ${literal(identifier.table)}` + return safeSql`${ident('name')} = ${literal(identifier.name)} and ${ident('schema')} = ${literal(identifier.schema)} and ${ident('table')} = ${literal(identifier.table)}` } throw new Error('Must provide either id or name, schema and table') } @@ -51,23 +51,23 @@ export function list({ limit?: number offset?: number } = {}): { - sql: string + sql: SafeSqlFragment zod: typeof pgTriggerArrayZod } { - let sql = `with triggers as (${TRIGGERS_SQL}) select * from triggers` + let sql = safeSql`with triggers as (${TRIGGERS_SQL}) select * from triggers` const filter = filterByList( includedSchemas, excludedSchemas, !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined ) if (filter) { - sql += ` where schema ${filter}` + sql = safeSql`${sql} where schema ${filter}` } if (limit) { - sql += ` limit ${limit}` + sql = safeSql`${sql} limit ${literal(limit)}` } if (offset) { - sql += ` offset ${offset}` + sql = safeSql`${sql} offset ${literal(offset)}` } return { sql, @@ -76,14 +76,14 @@ export function list({ } type TriggersRetrieveReturn = { - sql: string + sql: SafeSqlFragment zod: typeof pgTriggerOptionalZod } export function retrieve(identifier: TriggerIdentifier): TriggersRetrieveReturn export function retrieve(params: TriggerIdentifier): TriggersRetrieveReturn { const whereIdentifierCondition = getIdentifierWhereClause(params) - const sql = `with triggers as (${TRIGGERS_SQL}) select * from triggers where ${whereIdentifierCondition};` + const sql = safeSql`with triggers as (${TRIGGERS_SQL}) select * from triggers where ${whereIdentifierCondition};` return { sql, zod: pgTriggerOptionalZod, @@ -148,23 +148,23 @@ export function update( id: { name: string; schema: string; table: string }, params: PGTriggerUpdate ): { - sql: string + sql: SafeSqlFragment zod: z.ZodType } { - const qualifiedTableName = `${ident(id.schema)}.${ident(id.table)}` + const qualifiedTableName = safeSql`${ident(id.schema)}.${ident(id.table)}` - let enabledModeSql = '' + let enabledModeSql = safeSql`` switch (params.enabled_mode) { case 'ORIGIN': - enabledModeSql = `alter table ${qualifiedTableName} enable trigger ${ident(id.name)};` + enabledModeSql = safeSql`alter table ${qualifiedTableName} enable trigger ${ident(id.name)};` break case 'DISABLED': - enabledModeSql = `alter table ${qualifiedTableName} disable trigger ${ident(id.name)};` + enabledModeSql = safeSql`alter table ${qualifiedTableName} disable trigger ${ident(id.name)};` break case 'REPLICA': case 'ALWAYS': - enabledModeSql = `alter table ${qualifiedTableName} enable ${params.enabled_mode} trigger ${ident(id.name)};` + enabledModeSql = safeSql`alter table ${qualifiedTableName} enable ${keyword(params.enabled_mode)} trigger ${ident(id.name)};` break default: break @@ -172,11 +172,11 @@ export function update( const updateNameSql = params.name && params.name !== id.name - ? `alter trigger ${ident(id.name)} on ${qualifiedTableName} rename to ${ident(params.name)};` - : '' + ? safeSql`alter trigger ${ident(id.name)} on ${qualifiedTableName} rename to ${ident(params.name)};` + : safeSql`` // updateNameSql must be last - const sql = `begin; ${enabledModeSql}; ${updateNameSql}; commit;` + const sql = safeSql`begin; ${enabledModeSql}; ${updateNameSql}; commit;` return { sql, @@ -188,12 +188,12 @@ export function remove( id: { name: string; schema: string; table: string }, { cascade = false } = {} ): { - sql: string + sql: SafeSqlFragment zod: z.ZodType } { - const qualifiedTableName = `${ident(id.schema)}.${ident(id.table)}` + const qualifiedTableName = safeSql`${ident(id.schema)}.${ident(id.table)}` - const sql = `drop trigger ${ident(id.name)} on ${qualifiedTableName} ${cascade ? 'cascade' : ''};` + const sql = safeSql`drop trigger ${ident(id.name)} on ${qualifiedTableName} ${cascade ? safeSql`cascade` : safeSql``};` return { sql, diff --git a/packages/pg-meta/src/pg-meta-types.ts b/packages/pg-meta/src/pg-meta-types.ts index 33371f3d5d9..547070d51d2 100644 --- a/packages/pg-meta/src/pg-meta-types.ts +++ b/packages/pg-meta/src/pg-meta-types.ts @@ -2,6 +2,7 @@ import { z } from 'zod' import { DEFAULT_SYSTEM_SCHEMAS } from './constants' import { filterByList } from './helpers' +import { literal, safeSql, type SafeSqlFragment } from './pg-format' import { TYPES_SQL } from './sql/types' const pgTypeZod = z.object({ @@ -36,12 +37,12 @@ function list({ limit?: number offset?: number } = {}): { - sql: string + sql: SafeSqlFragment zod: typeof pgTypeArrayZod } { let sql = TYPES_SQL if (!includeArrayTypes) { - sql += ` and not exists ( + sql = safeSql`${sql} and not exists ( select from pg_type el where el.oid = t.typelem and el.typarray = t.oid @@ -53,13 +54,13 @@ function list({ !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined ) if (filter) { - sql += ` and n.nspname ${filter}` + sql = safeSql`${sql} and n.nspname ${filter}` } if (limit) { - sql += ` limit ${limit}` + sql = safeSql`${sql} limit ${literal(limit)}` } if (offset) { - sql += ` offset ${offset}` + sql = safeSql`${sql} offset ${literal(offset)}` } return { sql, diff --git a/packages/pg-meta/src/pg-meta-version.ts b/packages/pg-meta/src/pg-meta-version.ts index 443da64f13b..2423279addf 100644 --- a/packages/pg-meta/src/pg-meta-version.ts +++ b/packages/pg-meta/src/pg-meta-version.ts @@ -1,5 +1,6 @@ import { z } from 'zod' +import { type SafeSqlFragment } from './pg-format' import { VERSION_SQL } from './sql/version' export const pgVersionZod = z.object({ @@ -9,7 +10,7 @@ export const pgVersionZod = z.object({ max_connections: z.number(), }) -function retrieve() { +function retrieve(): { sql: SafeSqlFragment; zod: typeof pgVersionZod } { return { sql: VERSION_SQL, zod: pgVersionZod, diff --git a/packages/pg-meta/src/sql/config.ts b/packages/pg-meta/src/sql/config.ts index c870ff182ba..c289d5e49f9 100644 --- a/packages/pg-meta/src/sql/config.ts +++ b/packages/pg-meta/src/sql/config.ts @@ -1,4 +1,6 @@ -export const CONFIG_SQL = /* SQL */ ` +import { safeSql } from '../pg-format' + +export const CONFIG_SQL = /* SQL */ safeSql` SELECT name, setting, diff --git a/packages/pg-meta/src/sql/extensions.ts b/packages/pg-meta/src/sql/extensions.ts index 403c1c2d20a..2bd5a718a4c 100644 --- a/packages/pg-meta/src/sql/extensions.ts +++ b/packages/pg-meta/src/sql/extensions.ts @@ -1,4 +1,6 @@ -export const EXTENSIONS_SQL = /* SQL */ ` +import { safeSql } from '../pg-format' + +export const EXTENSIONS_SQL = /* SQL */ safeSql` SELECT e.name, n.nspname AS schema, diff --git a/packages/pg-meta/src/sql/functions.ts b/packages/pg-meta/src/sql/functions.ts index 996d13fd253..257cb7bc5bf 100644 --- a/packages/pg-meta/src/sql/functions.ts +++ b/packages/pg-meta/src/sql/functions.ts @@ -1,4 +1,6 @@ -export const FUNCTIONS_SQL = /* SQL */ ` +import { safeSql } from '../pg-format' + +export const FUNCTIONS_SQL = /* SQL */ safeSql` -- 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. diff --git a/packages/pg-meta/src/sql/indexes.ts b/packages/pg-meta/src/sql/indexes.ts index ef7cd40e5f1..db66dc9ab25 100644 --- a/packages/pg-meta/src/sql/indexes.ts +++ b/packages/pg-meta/src/sql/indexes.ts @@ -1,4 +1,6 @@ -export const INDEXES_SQL = /* SQL */ ` +import { safeSql } from '../pg-format' + +export const INDEXES_SQL = /* SQL */ safeSql` SELECT idx.indexrelid::int8 AS id, idx.indrelid::int8 AS table_id, diff --git a/packages/pg-meta/src/sql/policies.ts b/packages/pg-meta/src/sql/policies.ts index 66ff548ed8f..00103957959 100644 --- a/packages/pg-meta/src/sql/policies.ts +++ b/packages/pg-meta/src/sql/policies.ts @@ -1,4 +1,6 @@ -export const POLICIES_SQL = /* SQL */ ` +import { safeSql } from '../pg-format' + +export const POLICIES_SQL = /* SQL */ safeSql` select pol.oid :: int8 as id, n.nspname as schema, diff --git a/packages/pg-meta/src/sql/publications.ts b/packages/pg-meta/src/sql/publications.ts index ea778615d32..f1a38dddc91 100644 --- a/packages/pg-meta/src/sql/publications.ts +++ b/packages/pg-meta/src/sql/publications.ts @@ -1,4 +1,6 @@ -export const PUBLICATIONS_SQL = /* SQL */ ` +import { safeSql } from '../pg-format' + +export const PUBLICATIONS_SQL = /* SQL */ safeSql` SELECT p.oid :: int8 AS id, p.pubname AS name, diff --git a/packages/pg-meta/src/sql/roles.ts b/packages/pg-meta/src/sql/roles.ts index 7dab24f5967..044f77ada5a 100644 --- a/packages/pg-meta/src/sql/roles.ts +++ b/packages/pg-meta/src/sql/roles.ts @@ -1,4 +1,6 @@ -export const ROLES_SQL = /* SQL */ ` +import { safeSql } from '../pg-format' + +export const ROLES_SQL = /* SQL */ safeSql` -- Can't use pg_authid here since some managed Postgres providers don't expose it -- https://github.com/supabase/postgres-meta/issues/212 diff --git a/packages/pg-meta/src/sql/schemas.ts b/packages/pg-meta/src/sql/schemas.ts index 092e78495b8..8131f62d633 100644 --- a/packages/pg-meta/src/sql/schemas.ts +++ b/packages/pg-meta/src/sql/schemas.ts @@ -1,4 +1,6 @@ -export const SCHEMAS_SQL = /* SQL */ ` +import { safeSql } from '../pg-format' + +export const SCHEMAS_SQL = /* SQL */ safeSql` -- Adapted from information_schema.schemata select diff --git a/packages/pg-meta/src/sql/table-privileges.ts b/packages/pg-meta/src/sql/table-privileges.ts index ad54a2103c6..bd2e031783d 100644 --- a/packages/pg-meta/src/sql/table-privileges.ts +++ b/packages/pg-meta/src/sql/table-privileges.ts @@ -1,4 +1,6 @@ -export const TABLE_PRIVILEGES_SQL = /* SQL */ ` +import { safeSql } from '../pg-format' + +export const TABLE_PRIVILEGES_SQL = /* SQL */ safeSql` -- Despite the name \`table_privileges\`, this includes other kinds of relations: -- views, matviews, etc. "Relation privileges" just doesn't roll off the tongue. -- diff --git a/packages/pg-meta/src/sql/triggers.ts b/packages/pg-meta/src/sql/triggers.ts index 4d1f6962328..7f3ec27dd71 100644 --- a/packages/pg-meta/src/sql/triggers.ts +++ b/packages/pg-meta/src/sql/triggers.ts @@ -1,4 +1,6 @@ -export const TRIGGERS_SQL = /* SQL */ ` +import { safeSql } from '../pg-format' + +export const TRIGGERS_SQL = /* SQL */ safeSql` SELECT pg_t.oid AS id, pg_t.tgrelid AS table_id, diff --git a/packages/pg-meta/src/sql/types.ts b/packages/pg-meta/src/sql/types.ts index cc73a14c33c..b3da5666d24 100644 --- a/packages/pg-meta/src/sql/types.ts +++ b/packages/pg-meta/src/sql/types.ts @@ -1,4 +1,6 @@ -export const TYPES_SQL = /* SQL */ ` +import { safeSql } from '../pg-format' + +export const TYPES_SQL = /* SQL */ safeSql` select t.oid::int8 as id, t.typname as name, diff --git a/packages/pg-meta/src/sql/version.ts b/packages/pg-meta/src/sql/version.ts index 7a5104e4010..99bb8dc3219 100644 --- a/packages/pg-meta/src/sql/version.ts +++ b/packages/pg-meta/src/sql/version.ts @@ -1,4 +1,6 @@ -export const VERSION_SQL = /* SQL */ ` +import { safeSql } from '../pg-format' + +export const VERSION_SQL = /* SQL */ safeSql` select version(), current_setting('server_version_num')::int8 as version_number,