UNPKG

cdk-rds-sql

Version:

A CDK construct that allows creating roles and databases an on Aurora Serverless Postgresql cluster.

252 lines (209 loc) 6.47 kB
// reserved Postgres words import reservedMap from './reserved' const fmtPattern = { ident: 'I', literal: 'L', string: 's', } // convert to Postgres default ISO 8601 format function formatDate(date:string): string { date = date.replace('T', ' ') date = date.replace('Z', '+00') return date } function isReserved(value:string):boolean { if (reservedMap[value.toUpperCase()]) { return true } return false } function arrayToList(useSpace:boolean, array:any[], formatter:(value:any)=>string) { let sql = '' sql += useSpace ? ' (' : '(' for (let i = 0; i < array.length; i++) { sql += (i === 0 ? '' : ', ') + formatter(array[i]) } sql += ')' return sql } // Ported from PostgreSQL 9.2.4 source code in src/interfaces/libpq/fe-exec.c export function quoteIdent(value:any): string { if (value === undefined || value === null) { throw new Error('SQL identifier cannot be null or undefined') } else if (value === false) { return '"f"' } else if (value === true) { return '"t"' } else if (value instanceof Date) { return '"' + formatDate(value.toISOString()) + '"' } else if (value instanceof Buffer) { throw new Error('SQL identifier cannot be a buffer') } else if (Array.isArray(value) === true) { const temp: string[] = [] for (let i = 0; i < value.length; i++) { if (Array.isArray(value[i]) === true) { throw new Error('Nested array to grouped list conversion is not supported for SQL identifier') } else { temp.push(quoteIdent(value[i])) } } return temp.toString() } else if (value === Object(value)) { throw new Error('SQL identifier cannot be an object') } const ident = value.toString().slice(0) // create copy // do not quote a valid, unquoted identifier // https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS if (/^[a-zA-Z_][a-zA-Z0-9_$.]*$/.test(ident) === true && isReserved(ident) === false) { return ident } let quoted = '"' for (let i = 0; i < ident.length; i++) { const c = ident[i] if (c === '"') { quoted += c + c } else { quoted += c } } quoted += '"' return quoted } // Ported from PostgreSQL 9.2.4 source code in src/interfaces/libpq/fe-exec.c export function quoteLiteral(value) { let literal = '' let explicitCast: string | null = null if (value === undefined || value === null) { return 'NULL' } else if (typeof value === 'bigint') { return BigInt(value).toString() } else if (value === Number.POSITIVE_INFINITY) { return "'Infinity'" } else if (value === Number.NEGATIVE_INFINITY) { return "'-Infinity'" } else if (Number.isNaN(value)) { return "'NaN'" } else if (typeof value === 'number') {//Test must be AFTER other special case number tests return Number(value).toString() } else if (value === false) { return "'f'" } else if (value === true) { return "'t'" } else if (value instanceof Date) { return "'" + formatDate(value.toISOString()) + "'" } else if (value instanceof Buffer) { return "E'\\\\x" + value.toString('hex') + "'" } else if (Array.isArray(value) === true) { const temp: string[] = [] for (let i = 0; i < value.length; i++) { if (Array.isArray(value[i]) === true) { temp.push(arrayToList(i !== 0, value[i], quoteLiteral)) } else { temp.push(quoteLiteral(value[i])) } } return temp.toString() } else if (value === Object(value)) { explicitCast = 'jsonb' literal = JSON.stringify(value) } else { literal = value.toString().slice(0) // create copy } let hasBackslash = false let quoted = '\'' for (let i = 0; i < literal.length; i++) { const c = literal[i] if (c === '\'') { quoted += c + c } else if (c === '\\') { quoted += c + c hasBackslash = true } else { quoted += c } } quoted += '\'' if (hasBackslash === true) { quoted = 'E' + quoted } if (explicitCast) { quoted += '::' + explicitCast } return quoted } export function quoteString(value): string { if (value === undefined || value === null) { return '' } else if (value === false) { return 'f' } else if (value === true) { return 't' } else if (value instanceof Date) { return formatDate(value.toISOString()) } else if (value instanceof Buffer) { return '\\x' + value.toString('hex') } else if (Array.isArray(value) === true) { const temp: string[] = [] for (let i = 0; i < value.length; i++) { if (value[i] !== null && value[i] !== undefined) { if (Array.isArray(value[i]) === true) { temp.push(arrayToList(i !== 0, value[i], quoteString)) } else { temp.push(quoteString(value[i])) } } } return temp.toString() } else if (value === Object(value)) { return JSON.stringify(value) } return value.toString().slice(0) // return copy } export function config(cfg) { // default fmtPattern.ident = 'I' fmtPattern.literal = 'L' fmtPattern.string = 's' if (cfg && cfg.pattern) { if (cfg.pattern.ident) { fmtPattern.ident = cfg.pattern.ident } if (cfg.pattern.literal) { fmtPattern.literal = cfg.pattern.literal } if (cfg.pattern.string) { fmtPattern.string = cfg.pattern.string } } } export function formatWithArray(fmt, parameters) { let index = 0 let params = parameters let reText = '%(%|(\\d+\\$)?[' reText += fmtPattern.ident reText += fmtPattern.literal reText += fmtPattern.string reText += '])' const re = new RegExp(reText, 'g') return fmt.replace(re, function (_, type) { if (type === '%') { return '%' } let position = index const tokens = type.split('$') if (tokens.length > 1) { position = parseInt(tokens[0]) - 1 type = tokens[1] } if (position < 0) { throw new Error('specified argument 0 but arguments start at 1') } else if (position > params.length - 1) { throw new Error('too few arguments') } index = position + 1 if (type === fmtPattern.ident) { return quoteIdent(params[position]) } else if (type === fmtPattern.literal) { return quoteLiteral(params[position]) } else if (type === fmtPattern.string) { return quoteString(params[position]) } }) } export function format(fmt: string, ...args: any[]): string { return formatWithArray(fmt, args) }