Skip to main content
Module

x/pgsql_ast_parser/to-sql.ts

Yet another simple Postgres SQL parser
Go to Latest
File
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462
import { IAstPartialMapper, AstDefaultMapper } from './ast-mapper.ts';import { astVisitor, IAstVisitor, IAstFullVisitor } from './ast-visitor.ts';import { NotSupported, nil, ReplaceReturnType, NoExtraProperties } from './utils.ts';import { TableConstraint, JoinClause, ColumnConstraint, AlterSequenceStatement, CreateSequenceStatement, AlterSequenceSetOptions, CreateSequenceOptions, QName, SetGlobalValue, AlterColumnAddGenerated, QColumn, Name, OrderByStatement, QNameAliased } from './syntax/ast.ts';import { literal } from './pg-escape.ts';import { sqlKeywords } from './keywords.ts';


export type IAstToSql = { readonly [key in keyof IAstPartialMapper]-?: ReplaceReturnType<IAstPartialMapper[key], string> }
const kwSet = new Set(sqlKeywords.map(x => x.toLowerCase()));

let ret: string[] = [];

function name<T extends Name>(nm: NoExtraProperties<Name, T>) { return ident(nm.name);}
function ident(nm: string, forceDoubleQuote?: boolean) { if (!forceDoubleQuote) { // only add quotes if has upper cases, or if it is a keyword. const low = nm.toLowerCase(); if (low === nm && !kwSet.has(low) && /^[a-z][a-z0-9_]*$/.test(low)) { return nm; } } return '"' + nm + '"';}
function list<T>(elems: T[], act: (e: T) => any, addParen: boolean) { if (addParen) { ret.push('('); } let first = true; for (const e of elems) { if (!first) { ret.push(', '); } first = false; act(e); } if (addParen) { ret.push(')'); }}

function addConstraint(c: ColumnConstraint | TableConstraint, m: IAstVisitor) { switch (c.type) { case 'foreign key': ret.push(' foreign key (' , ...c.localColumns.map(name).join(', ') , ')'); // 👈 There is no "break" here... that's not an error, we want to fall throught the 'reference' case case 'reference': ret.push(' REFERENCES '); m.tableRef(c.foreignTable); ret.push('(' , ...c.foreignColumns.map(name).join(', ') , ') '); if (c.match) { ret.push(' MATCH ', c.match.toUpperCase()); } if (c.onDelete) { ret.push(' ON DELETE ', c.onDelete); } if (c.onUpdate) { ret.push(' ON UPDATE ', c.onUpdate); } break; case 'primary key': case 'unique': ret.push(' ', c.type, ' '); if ('columns' in c) { ret.push('(' , ...c.columns.map(name).join(', ') , ') '); } break; case 'check': ret.push(' check '); m.expr(c.expr); break; case 'not null': case 'null': ret.push(' ', c.type, ' '); break; case 'default': ret.push(' default '); m.expr(c.default); break; case 'add generated': ret.push(' GENERATED '); visitGenerated(m, c); break; default: throw NotSupported.never(c) } ret.push(' ');}function visitQualifiedName(cs: QName, forceDoubleQuote?: boolean) { if (cs.schema) { ret.push(ident(cs.schema), '.'); } ret.push(ident(cs.name, forceDoubleQuote), ' ');}
function visitQualifiedNameAliased(cs: QNameAliased) { visitQualifiedName(cs); if (cs.alias) { ret.push(' AS ', ident(cs.alias), ' '); }}
function visitOrderBy(m: IAstVisitor, orderBy: OrderByStatement[]) { ret.push(' ORDER BY '); list(orderBy, e => { m.expr(e.by); if (e.order) { ret.push(' ', e.order, ' '); } if (e.nulls) { ret.push(' NULLS ', e.nulls, ' ') } }, false);}
function visitSetVal(set: SetGlobalValue) {
switch (set.type) { case 'default': ret.push('DEFAULT '); break; case 'identifier': ret.push(set.name); break; case 'list': let first = true; for (const v of set.values) { if (!first) { ret.push(', '); } first = false; visitSetVal(v); } break; case 'value': ret.push(typeof set.value === 'number' ? set.value.toString() : literal(set.value)); break; default: throw NotSupported.never(set); }}
function visitGenerated(m: IAstVisitor, alter: AlterColumnAddGenerated) { if (alter.always) { ret.push(alter.always.toUpperCase(), ' '); } ret.push('AS IDENTITY '); if (alter.sequence) { ret.push('('); if (alter.sequence.name) { ret.push('SEQUENCE NAME '); visitQualifiedName(alter.sequence.name); ret.push(' '); } visitSeqOpts(m, alter.sequence); ret.push(') '); }}function visitSeqOpts(m: IAstVisitor, cs: AlterSequenceSetOptions | CreateSequenceOptions) { if (cs.as) { ret.push('AS '); m.dataType(cs.as); ret.push(' '); } if (typeof cs.incrementBy === 'number') { ret.push('INCREMENT BY ', cs.incrementBy.toString(), ' '); } if (cs.minValue === 'no minvalue') { ret.push('NO MINVALUE '); } if (typeof cs.minValue === 'number') { ret.push('MINVALUE ', cs.minValue.toString(), ' '); } if (cs.maxValue === 'no maxvalue') { ret.push('NO MAXVALUE '); } if (typeof cs.maxValue === 'number') { ret.push('MAXVALUE ', cs.maxValue.toString(), ' '); } if (typeof cs.startWith === 'number') { ret.push('START WITH ', cs.startWith.toString(), ' '); } if (typeof cs.cache === 'number') { ret.push('CACHE ', cs.cache.toString(), ' '); } if (cs.cycle) { ret.push(cs.cycle, ' '); } if (cs.ownedBy === 'none') { ret.push('OWNED BY NONE '); } else if (cs.ownedBy) { ret.push('OWNED BY '); visitQColumn(cs.ownedBy); }
if ('restart' in cs) { if (cs.restart === true) { ret.push('RESTART ') } else if (cs.restart) { ret.push('RESTART WITH ', cs.restart.toString(), ' '); } }}
function visitQColumn(col: QColumn) { if (col.schema) { ret.push(ident(col.schema), '.'); } ret.push(ident(col.table), '.', ident(col.column), ' ');}
function join(m: IAstVisitor, j: JoinClause | nil, tbl: () => void) { if (!j) { tbl(); return; } ret.push(j.type, ' '); tbl(); if (j.on) { ret.push('ON ') m.expr(j.on); } if (j.using) { ret.push('USING ('); list(j.using, x => ret.push(name(x)), false); ret.push(') '); } ret.push(' ');}
function visitOp(v: { op: string; opSchema?: string; }) { if (v.opSchema) { ret.push(' operator(', ident(v.opSchema), '.', v.op, ') '); } else { ret.push(' ', v.op, ' '); }}
const visitor = astVisitor<IAstFullVisitor>(m => ({
addColumn: (...args) => { ret.push(' ADD COLUMN '); if (args[0].ifNotExists) { ret.push('IF NOT EXISTS '); } m.super().addColumn(...args); },
createExtension: e => { ret.push('CREATE EXTENSION '); if (e.ifNotExists) { ret.push(' IF NOT EXISTS '); } ret.push(name(e.extension)); if (!e.from && !e.version && !e.schema) { return; } ret.push(' WITH'); if (e.schema) { ret.push(' SCHEMA ', name(e.schema)); } if (e.version) { ret.push(' VERSION ', literal(e.version.value)); } if (e.from) { ret.push(' FROM ', literal(e.from.value)); } },
tablespace: t => { ret.push('TABLESPACE ', name(t.tablespace)); },
addConstraint: c => { ret.push(' ADD '); const cname = c.constraint.constraintName; if (cname) { ret.push(' CONSTRAINT ', name(cname), ' '); } addConstraint(c.constraint, m); },
alterColumn: (c, t) => { ret.push(' ALTER COLUMN ', name(c.column), ' '); m.super().alterColumn(c, t); },
setColumnDefault: (a, t, c) => { ret.push(' SET DEFAULT '); m.expr(a.default); if (a.updateExisting) { throw new Error('Not implemented: updateExisting on set column default'); } },
createEnum: t => { ret.push('CREATE TYPE '); visitQualifiedName(t.name); ret.push(' AS ENUM '); list(t.values, x => ret.push(literal(x.value)), true); ret.push(' '); },
createCompositeType: c => { ret.push('CREATE TYPE '); visitQualifiedName(c.name); ret.push(' AS '); list(c.attributes, x => { ret.push(name(x.name), ' '); m.dataType(x.dataType); if (x.collate) { ret.push('COLLATE '); visitQualifiedName(x.collate); } }, true); ret.push(' '); },
setTableOwner: o => { ret.push(' OWNER TO ', name(o.to)); },
alterColumnSimple: c => ret.push(c.type),


alterColumnAddGenerated: (alter) => { ret.push(' ADD GENERATED '); visitGenerated(m, alter) },
setColumnType: t => { ret.push(' SET DATA TYPE '); m.dataType(t.dataType); ret.push(' '); },
alterTable: t => { ret.push('ALTER TABLE '); if (t.ifExists) { ret.push(' IF EXISTS '); } if (t.only) { ret.push(' ONLY '); } visitQualifiedNameAliased(t.table); list(t.changes, change => m.tableAlteration(change, t.table), false); },
tableAlteration: (change, table) => { switch (change.type) { case 'add column': return m.addColumn(change, table); case 'add constraint': return m.addConstraint(change, table); case 'alter column': return m.alterColumn(change, table); case 'rename': return m.renameTable(change, table); case 'rename column': return m.renameColumn(change, table); case 'rename constraint': return m.renameConstraint(change, table); case 'drop column': return m.dropColumn(change, table); case 'drop constraint': return m.dropConstraint(change, table); case 'owner': return m.setTableOwner(change, table); default: throw NotSupported.never(change); } },
array: v => { ret.push(v.type === 'array' ? 'ARRAY[' : '('); list(v.expressions, e => m.expr(e), false); ret.push(v.type === 'array' ? ']' : ')'); },
arrayIndex: v => { m.expr(v.array); ret.push('['); m.expr(v.index); ret.push('] '); },
expr: e => { if (e.type === 'ref') { m.ref(e); return; } // lists can become incorrect with an additional set of parentheses if (e.type === 'list') { m.super().expr(e); return; }
// this forces to respect precedence // (however, it will introduce lots of unecessary parenthesis) ret.push('('); m.super().expr(e); ret.push(')'); },
callOverlay: o => { ret.push('OVERLAY('); m.expr(o.value); ret.push(' PLACING '); m.expr(o.placing); ret.push(' FROM '); m.expr(o.from); if (o.for) { ret.push(' FOR '); m.expr(o.for); } ret.push(')'); },
callSubstring: s => { ret.push('SUBSTRING('); m.expr(s.value); if (s.from) { ret.push(' FROM '); m.expr(s.from); } if (s.for) { ret.push(' FOR '); m.expr(s.for); } ret.push(')'); },
binary: v => { m.expr(v.left); visitOp(v); m.expr(v.right); },
call: v => { visitQualifiedName(v.function); ret.push('('); if (v.distinct) { ret.push(v.distinct, ' '); } list(v.args, e => m.expr(e), false); if (v.orderBy) { visitOrderBy(m, v.orderBy); } ret.push(') '); if (v.filter) { ret.push('filter (where '); m.expr(v.filter); ret.push(') '); } if (v.over) { ret.push('over ('); if (v.over.partitionBy) { ret.push('PARTITION BY '); list(v.over.partitionBy, x => m.expr(x), false); ret.push(' '); } if (v.over.orderBy) { visitOrderBy(m, v.over.orderBy); ret.push(' '); } ret.push(') '); } },
case: c => { ret.push('CASE ') if (c.value) { m.expr(c.value); }
for (const e of c.whens) { ret.push(' WHEN '); m.expr(e.when); ret.push(' THEN ') m.expr(e.value); }
if (c.else) { ret.push(' ELSE '); m.expr(c.else); } ret.push(' END '); },
cast: c => { m.expr(c.operand); ret.push('::'); m.dataType(c.to); },
constant: c => { switch (c.type) { case 'boolean': ret.push(c.value ? 'true' : 'false'); break; case 'integer': ret.push(c.value.toString(10)); break; case 'numeric': ret.push(c.value.toString()); if (Number.isInteger(c.value)) { ret.push('.'); } break; case 'null': ret.push('null'); break; case 'constant': break; case 'string': ret.push(literal(c.value)); break; default: throw NotSupported.never(c); } },
valueKeyword: v => { ret.push(v.keyword, ' '); },
comment: c => { ret.push('COMMENT ON ', c.on.type.toUpperCase(), ' '); switch (c.on.type) { case 'column': visitQColumn(c.on.column); break; default: visitQualifiedName(c.on.name); break; } ret.push(' IS ', literal(c.comment), ' '); },
extract: v => { ret.push('EXTRACT (', v.field.name.toUpperCase(), ' FROM '); m.expr(v.from); ret.push(') '); },
createColumn: c => { ret.push(name(c.name), ' '); m.dataType(c.dataType); ret.push(' '); if (c.collate) { ret.push('COLLATE '); visitQualifiedName(c.collate); } for (const cst of c.constraints ?? []) { m.constraint(cst); } },
begin: beg => { ret.push('BEGIN '); if (beg.isolationLevel) { ret.push('ISOLATION LEVEL ', beg.isolationLevel.toUpperCase(), ' '); } if (beg.writeable) { ret.push(beg.writeable.toUpperCase(), ' '); } if (typeof beg.deferrable === 'boolean') { if (!beg.deferrable) { ret.push('NOT '); } ret.push('DEFERRABLE '); } },
alterSequence: cs => { ret.push('ALTER SEQUENCE '); if (cs.ifExists) { ret.push('IF EXISTS '); } visitQualifiedName(cs.name); switch (cs.change.type) { case 'set options': visitSeqOpts(m, cs.change); break; case 'rename': ret.push('RENAME TO ', name(cs.change.newName), ' '); break; case 'set schema': ret.push('SET SCHEMA ', name(cs.change.newSchema), ' '); break; case 'owner to': const own = cs.change.owner; ret.push('OWNER TO ', name(cs.change.owner), ' '); break; default: throw NotSupported.never(cs.change); } },
createSequence: cs => { ret.push('CREATE '); if (cs.temp) { ret.push('TEMPORARY '); } ret.push('SEQUENCE '); if (cs.ifNotExists) { ret.push('IF NOT EXISTS '); } visitQualifiedName(cs.name); visitSeqOpts(m, cs.options); },

drop: val => { ret.push(val.type.toUpperCase(), ' '); if (val.concurrently) { ret.push('CONCURRENTLY '); } if (val.ifExists) { ret.push('IF EXISTS '); } list(val.names, x => m.tableRef(x), false); if (val.cascade) { ret.push(val.cascade.toUpperCase(), ' '); } },
constraint: cst => { if (cst.constraintName) { ret.push(' CONSTRAINT ', name(cst.constraintName), ' '); } addConstraint(cst, m); },
do: d => { ret.push('DO'); if (d.language) { ret.push(' LANGUAGE ', d.language.name); } ret.push(' $$', d.code, '$$'); },
createFunction: c => { ret.push(c.orReplace ? 'CREATE OR REPLACE FUNCTION ' : 'CREATE FUNCTION ');
visitQualifiedName(c.name);
// args list(c.arguments, a => { if (a.mode) { ret.push(a.mode, ' '); } if (a.name) { ret.push(name(a.name), ' '); } m.dataType(a.type); if (a.default) { ret.push(" = "); m.expr(a.default); } }, true);
// ret type if (c.returns) { switch (c.returns.kind) { case 'table': ret.push(' RETURNS TABLE '); list(c.returns.columns, t => { ret.push(name(t.name), ' '); m.dataType(t.type); }, true); break; case undefined: case null: case 'array': ret.push(' RETURNS '); m.dataType(c.returns); break; default: throw NotSupported.never(c.returns); } }
ret.push(' AS $$', c.code ?? '', '$$');
// function settings if (c.language) { ret.push('LANGUAGE ', c.language.name, ' '); } if (c.purity) { ret.push(c.purity.toUpperCase(), ' '); } if (typeof c.leakproof === 'boolean') { ret.push(c.leakproof ? 'LEAKPROOF ' : 'NOT LEAKPROOF '); } switch (c.onNullInput) { case 'call': ret.push('CALLED ON NULL INPUT '); break; case 'null': ret.push('RETURNS NULL ON NULL INPUT '); break; case 'strict': ret.push('STRICT '); break; case null: case undefined: break; default: throw NotSupported.never(c.onNullInput); } },

dropFunction: d => { ret.push('DROP FUNCTION '); if (d.ifExists) { ret.push('IF EXISTS '); } visitQualifiedName(d.name);
if (d.arguments) { list(d.arguments, a => { if (a.name) { visitQualifiedName(a.name); ret.push(' '); } m.dataType(a.type); }, true); } ret.push(' '); },
with: w => { ret.push('WITH '); list(w.bind, b => { ret.push(name(b.alias), ' AS ('); m.statement(b.statement); ret.push(') '); }, false);
m.statement(w.in); },
withRecursive: val => { ret.push('WITH RECURSIVE ' , name(val.alias) , '(' , ...val.columnNames.map(name).join(', ') , ') AS ('); m.union(val.bind); ret.push(') '); m.statement(val.in); },

setGlobal: g => { ret.push('SET ', name(g.variable), ' = '); visitSetVal(g.set); },
setTimezone: g => { ret.push('SET TIME ZONE '); switch (g.to.type) { case 'default': case 'local': ret.push(g.to.type.toUpperCase(), ' '); break; case 'value': ret.push(typeof g.to.value === 'string' ? literal(g.to.value) : g.to.value.toString(10)); break; case 'interval': ret.push('INTERVAL ', literal(g.to.value), ' HOUR TO MINUTE'); break; default: throw NotSupported.never(g.to); } },
dataType: d => { if (d?.kind === 'array') { m.dataType(d.arrayOf!) ret.push('[]'); return; } if (!d?.name) { ret.push('unkown'); return; } let appendConfig = true; if (d.schema) { visitQualifiedName(d, d.doubleQuoted); } else { // see https://www.postgresql.org/docs/13/datatype.html // & issue https://github.com/oguimbal/pgsql-ast-parser/issues/38 if (d.doubleQuoted) { visitQualifiedName(d, true); } else { switch (d.name) { case 'double precision': case 'character varying': case 'bit varying': ret.push(d.name, ' '); break; case 'time without time zone': case 'timestamp without time zone': case 'time with time zone': case 'timestamp with time zone': const parts = d.name.split(' ');
ret.push(parts.shift()!); if (d.config?.length) { list(d.config, v => ret.push(v.toString(10)), true); } ret.push(' ');
ret.push(parts.join(' '), ' '); appendConfig = false; break; default: visitQualifiedName(d); break; } } }
if (appendConfig && d.config?.length) { list(d.config, v => ret.push(v.toString(10)), true); } },
createIndex: c => { ret.push(c.unique ? 'CREATE UNIQUE INDEX ' : 'CREATE INDEX '); if (c.ifNotExists) { ret.push(' IF NOT EXISTS '); } if (c.indexName) { ret.push(name(c.indexName), ' '); } ret.push('ON '); m.tableRef(c.table); if (c.using) { ret.push('USING ', name(c.using), ' '); } list(c.expressions, e => { m.expr(e.expression); ret.push(' '); if (e.collate) { ret.push('COLLATE '); visitQualifiedName(e.collate); } if (e.opclass) { visitQualifiedName(e.opclass); } if (e.order) { ret.push(e.order, ' '); } if (e.nulls) { ret.push('nulls ', e.nulls, ' '); } }, true); if (c.with) { ret.push('WITH '); list(c.with, w => { ret.push(w.parameter, ' = ', literal(w.value)); }, true); } if (c.tablespace) { ret.push('TABLESPACE ', ident(c.tablespace)); } if (c.where) { ret.push(' WHERE '); m.expr(c.where); } ret.push(' '); },
createTable: t => { ret.push('CREATE '); if (t.locality) { ret.push(t.locality.toUpperCase(), ' '); } if (t.temporary) { ret.push('TEMPORARY '); } if (t.unlogged) { ret.push('UNLOGGED '); } ret.push(t.ifNotExists ? 'TABLE IF NOT EXISTS ' : 'TABLE '); m.tableRef(t.name); ret.push('('); list(t.columns, c => { switch (c.kind) { case 'column': return m.createColumn(c); case 'like table': return m.likeTable(c); default: throw NotSupported.never(c); } }, false); if (t.constraints) { ret.push(', '); list(t.constraints, c => { const cname = c.constraintName; if (cname) { ret.push('CONSTRAINT ', name(cname), ' '); } addConstraint(c, m); }, false) } ret.push(') '); if (t.inherits?.length) { ret.push(' INHERITS '); list(t.inherits, i => visitQualifiedName(i), true); } },
likeTable: l => { ret.push(' LIKE '); m.tableRef(l.like); ret.push(' '); for (const { verb, option } of l.options) { ret.push(verb.toUpperCase(), ' ', option.toUpperCase(), ' '); } },
createSchema: s => { ret.push(s.ifNotExists ? 'CREATE SCHEMA IF NOT EXISTS ' : 'CREATE SCHEMA '); ret.push(name(s.name)); },
truncateTable: t => { ret.push('TRUNCATE TABLE '); let first = true; for (const tbl of t.tables) { if (!first) { ret.push(', '); } first = false; m.tableRef(tbl); } if (t.identity) { switch (t.identity) { case 'restart': ret.push(' RESTART IDENTITY '); break; case 'continue': ret.push(' CONTINUE IDENTITY '); break; } } if (t.cascade) { ret.push(' ', t.cascade, ' '); } },
delete: t => { ret.push('DELETE FROM '); m.tableRef(t.from); if (t.where) { ret.push(' WHERE '); m.expr(t.where); }
if (t.returning) { ret.push(' RETURNING '); list(t.returning, r => m.selectionColumn(r), false); } ret.push(' '); },
dropColumn: t => { ret.push(' DROP COLUMN '); if (t.ifExists) { ret.push(' IF EXISTS '); } ret.push(name(t.column)); },
dropConstraint: t => { ret.push(' DROP CONSTRAINT '); if (t.ifExists) { ret.push(' IF EXISTS '); } ret.push(name(t.constraint)); if (t.behaviour) { ret.push(' ', t.behaviour.toUpperCase(), ' '); } },
from: t => m.super().from(t),
fromCall: s => {
join(m, s.join, () => { m.call(s); if (s.withOrdinality) { ret.push(' WITH ORDINALITY') } if (s.alias) { ret.push(' AS ', name<Name>(s.alias), ' '); const len = s.alias.columns?.length ?? 0; if (len > 0) { ret.push('(') for (let ix = 0; ix < len; ++ix) { if (ix !== 0) { ret.push(', ') } ret.push(name(s.alias.columns![ix])); } ret.push(')') } } });
ret.push(' '); },
fromStatement: s => {
// todo: use 's.db' if defined join(m, s.join, () => { ret.push('('); m.select(s.statement); ret.push(') '); if (s.alias) { ret.push(' AS ', ident(s.alias)); if (s.columnNames) { list(s.columnNames, c => ret.push(name(c)), true); } ret.push(' '); } });
ret.push(' '); },
values: s => { ret.push('VALUES '); list(s.values, vlist => { list(vlist, e => { m.expr(e); }, true); }, false); },
fromTable: s => { join(m, s.join, () => { m.tableRef(s.name); if (s.name.columnNames) { if (!s.name.alias) { throw new Error('Cannot specify aliased column names without an alias'); } list(s.name.columnNames, c => ret.push(name(c)), true); } }); },
join: j => { throw new Error('Should not happen 💀'); },
insert: i => { ret.push('INSERT INTO '); m.tableRef(i.into);
if (i.columns) { ret.push( '(' , i.columns.map(name).join(', ') , ')' ); } ret.push(' '); if (i.overriding) { ret.push('OVERRIDING ', i.overriding.toUpperCase(), ' VALUE '); }
m.select(i.insert); ret.push(' ');
if (i.onConflict) { ret.push('ON CONFLICT '); if (i.onConflict.on) { list(i.onConflict.on, e => m.expr(e), true); } if (i.onConflict.do === 'do nothing') { ret.push(' DO NOTHING'); } else { ret.push(' DO UPDATE SET '); list(i.onConflict.do.sets, s => m.set(s), false); if (i.onConflict.where) { ret.push(' WHERE '); m.expr(i.onConflict.where); } } ret.push(' '); }
if (i.returning) { ret.push(' RETURNING '); list(i.returning, r => m.selectionColumn(r), false); } },
raise: r => { ret.push('RAISE '); if (r.level) { ret.push(r.level.toUpperCase(), ' '); } ret.push(literal(r.format), ' ');
if (r.formatExprs?.length) { ret.push(', '); list(r.formatExprs, e => m.expr(e), false); } if (r.using?.length) { ret.push(' USING '); list(r.using, ({ type, value }) => { ret.push(type.toUpperCase(), '='); m.expr(value); }, false); } ret.push(' '); },
default: () => { ret.push(' DEFAULT '); },
member: e => { m.expr(e.operand); ret.push(e.op); ret.push(typeof e.member === 'number' ? e.member.toString(10) : literal(e.member)); },
ref: r => { if (r.table) { visitQualifiedName(r.table); ret.push('.'); } ret.push(r.name === '*' ? '*' : ident(r.name)); },
parameter: p => { ret.push(p.name); },
renameColumn: r => { ret.push(' RENAME COLUMN ' , name(r.column) , ' TO ' , name(r.to)); },
renameConstraint: r => { ret.push(' RENAME CONSTRAINT ' , name(r.constraint) , ' TO ' , name(r.to)); },
renameTable: r => { ret.push(' RENAME TO ' , name(r.to)); },
createView: c => { ret.push('CREATE '); if (c.orReplace) { ret.push('OR REPLACE '); } if (c.temp) { ret.push('TEMP '); } if (c.recursive) { ret.push('RECURSIVE '); } ret.push('VIEW '); m.tableRef(c.name); if (c.columnNames) { list(c.columnNames, c => ret.push(name(c)), true); } const opts = c.parameters && Object.entries(c.parameters); if (opts?.length) { ret.push(' WITH '); list(opts, ([k, v]) => ret.push(k, '=', v), false); } ret.push(' AS '); m.select(c.query); if (c.checkOption) { ret.push(' WITH ', c.checkOption.toUpperCase(), ' CHECK OPTION'); } },
createMaterializedView: c => { ret.push('CREATE MATERIALIZED VIEW '); if (c.ifNotExists) { ret.push('IF NOT EXISTS '); } m.tableRef(c.name); if (c.columnNames) { list(c.columnNames, c => ret.push(name(c)), true); } const opts = c.parameters && Object.entries(c.parameters); if (opts?.length) { ret.push(' WITH '); list(opts, ([k, v]) => ret.push(k, '=', v), false); } if (c.tablespace) { ret.push(' TABLESPACE ', name(c.tablespace)); } ret.push(' AS '); m.select(c.query); if (typeof c.withData === 'boolean') { ret.push(c.withData ? ' WITH DATA' : ' WITH NO DATA'); } },
refreshMaterializedView: val => { ret.push('REFRESH MATERIALIZED VIEW '); if (val.concurrently) { ret.push('CONCURRENTLY '); } m.tableRef(val.name); if (typeof val.withData === 'boolean') { ret.push(val.withData ? ' WITH DATA' : ' WITH NO DATA'); } },
select: s => m.super().select(s),
selection: s => { ret.push('SELECT ');
if (s.distinct) { if (typeof s.distinct === 'string') { ret.push(s.distinct.toUpperCase()); } else { ret.push(' DISTINCT ON '); list(s.distinct, v => m.expr(v), true); } ret.push(' '); }
if (s.columns) { list(s.columns, c => m.selectionColumn(c), false); } ret.push(' '); if (s.from) { ret.push('FROM '); const tblCnt = s.from.length for (let i = 0; i < tblCnt; i++) { const f = s.from[i]; if (i > 0 && !f.join) { // implicit cross join (https://www.postgresql.org/docs/9.5/sql-select.html#SQL-FROM) ret.push(',') } m.from(f); } ret.push(' '); }
if (s.where) { ret.push('WHERE '); m.expr(s.where); ret.push(' '); }
if (s.groupBy) { ret.push('GROUP BY '); list(s.groupBy, e => m.expr(e), false); ret.push(' '); }
if (s.orderBy) { visitOrderBy(m, s.orderBy); ret.push(' '); }
if (s.limit) { if (s.limit.offset) { ret.push(`OFFSET `); m.expr(s.limit.offset);
} if (s.limit.limit) { ret.push(`LIMIT `); m.expr(s.limit.limit); } }
if (s.for) { ret.push('FOR ', s.for.type.toUpperCase()); } },
show: s => { ret.push('SHOW ', name(s.variable)); },
prepare: s => { ret.push('PREPARE ', name(s.name)); if (s.args?.length) { list(s.args, a => m.dataType(a), true); } ret.push(' AS '); m.statement(s.statement); },
deallocate: s => { ret.push('DEALLOCATE '); if ('name' in s.target) { ret.push(s.target.name); return; } ret.push('ALL') },
arraySelect: s => { ret.push('array('); m.select(s.select); ret.push(')'); },
union: s => { ret.push('('); m.statement(s.left); ret.push(') ', s.type.toUpperCase(), ' '); if (s.right.type === 'union' || s.right.type === 'union all') { m.union(s.right); } else { ret.push('('); m.statement(s.right); ret.push(')'); } },
selectionColumn: c => { m.expr(c.expr); if (c.alias) { ret.push(' AS ', name(c.alias)); } ret.push(' '); },
set: s => { ret.push(name(s.column), ' = '); m.expr(s.value); ret.push(' '); },

statement: s => m.super().statement(s),
tableRef: r => { visitQualifiedName(r); if (r.alias) { ret.push(' AS ', ident(r.alias)); } ret.push(' '); },

ternary: t => { m.expr(t.value); ret.push(' ', t.op, ' '); m.expr(t.lo); ret.push(' AND '); m.expr(t.hi); ret.push(' '); },
transaction: t => { ret.push(t.type); },
unary: t => { switch (t.op) { case '+': case '-': // prefix ops visitOp(t); m.expr(t.operand); break; case 'NOT': // prefix ops ret.push(t.op); ret.push(' '); m.expr(t.operand); break; default: // postfix ops m.expr(t.operand); ret.push(' '); ret.push(t.op); } },
update: u => { ret.push('UPDATE '); m.tableRef(u.table); ret.push(' SET '); list(u.sets, s => m.set(s), false); ret.push(' '); if (u.from) { ret.push('FROM '); m.from(u.from); ret.push(' '); } if (u.where) { ret.push('WHERE '); m.expr(u.where); ret.push(' '); } if (u.returning) { ret.push(' RETURNING '); list(u.returning, r => m.selectionColumn(r), false); ret.push(' '); } },
}))
export const toSql = {} as IAstToSql;const proto = AstDefaultMapper.prototype as any;for (const k of Object.getOwnPropertyNames(proto)) { const orig = proto[k] as Function; if (k === 'constructor' || k === 'super' || typeof orig !== 'function') { continue; } (toSql as any)[k] = function (...args: []) { try { (visitor as any)[k].apply(visitor, args); return ret.join('').trim(); } finally { ret = []; } };}