mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
refactor(pg-meta): expand safeSql usage (#44674)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## 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. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
1 parent
054e44f7e6
commit
5d013cb2f6
25 files changed
+310
-260
No files matched your search
@@ -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<PGColumn, 'id'> | Pick<PGColumn, 'name' | 'schema' | 'table'>
|
||||
|
||||
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<PGColumn, 'name' | 'schema' | 'table'>,
|
||||
{ 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 }
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<typeof pgPolicyZod>
|
||||
|
||||
type PolicyIdentifier = Pick<PGPolicy, 'id'> | Pick<PGPolicy, 'name' | 'schema' | 'table'>
|
||||
|
||||
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<PGPolicy, 'name' | 'schema' | 'table'>): { sql: string } {
|
||||
const sql = `DROP POLICY ${ident(identifier.name)} ON ${ident(identifier.schema)}.${ident(
|
||||
identifier.table
|
||||
)};`
|
||||
function remove(identifier: Pick<PGPolicy, 'name' | 'schema' | 'table'>): { sql: SafeSqlFragment } {
|
||||
const sql = safeSql`DROP POLICY ${ident(identifier.name)} ON ${ident(identifier.schema)}.${ident(identifier.table)};`
|
||||
return { sql }
|
||||
}
|
||||
|
||||
|
||||
@@ -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<PGPublication, 'id'> | Pick<PGPublication, 'name'>
|
||||
|
||||
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<string> = []
|
||||
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<PGPublication, 'name'>): { sql: string } {
|
||||
const sql = `DROP PUBLICATION IF EXISTS ${ident(publication.name)};`
|
||||
function remove(publication: Pick<PGPublication, 'name'>): { sql: SafeSqlFragment } {
|
||||
const sql = safeSql`DROP PUBLICATION IF EXISTS ${ident(publication.name)};`
|
||||
return { sql }
|
||||
}
|
||||
|
||||
|
||||
@@ -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<PGRole, 'id'> | Pick<PGRole, 'name'>
|
||||
|
||||
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<string>
|
||||
members?: Array<string>
|
||||
admins?: Array<string>
|
||||
config?: Record<string, string>
|
||||
}
|
||||
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
|
||||
$$;
|
||||
`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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<PGTrigger, 'id'> | Pick<PGTrigger, 'name' | 'schema' | 'table'>
|
||||
|
||||
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<void>
|
||||
} {
|
||||
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<void>
|
||||
} {
|
||||
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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
export const CONFIG_SQL = /* SQL */ `
|
||||
import { safeSql } from '../pg-format'
|
||||
|
||||
export const CONFIG_SQL = /* SQL */ safeSql`
|
||||
SELECT
|
||||
name,
|
||||
setting,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
--
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user