UNPKG

pg-query-parser

Version:
1,362 lines (1,037 loc) 33.3 kB
const _ = require('lodash'); const { format } = require('util'); const compact = o => { return _.filter(_.compact(o), (p) => { if (p == null) { return false; } return p.toString().length; }); }; const fail = (type, node) => { throw new Error(format('Unhandled %s node: %s', type, JSON.stringify(node))); }; const parens = (string) => { return '(' + string + ')'; }; const indent = (text, count = 1) => text; class Deparser { static deparse(query) { return new Deparser(query).deparseQuery(); } constructor(tree) { this.tree = tree; } deparseQuery() { return (this.tree.map(node => this.deparse(node))).join(';\n\n'); } deparseNodes(nodes, context) { return nodes.map(node => this.deparse(node, context)); } list(nodes, separator = ', ') { if (!nodes) { return ''; } return this.deparseNodes(nodes).join(separator); } quote(value) { if (value == null) { return null; } if (_.isArray(value)) { return value.map(o => this.quote(o)); } return '"' + value + '"'; } // SELECT encode(E'''123\\000\\001', 'base64') escape(literal) { return "'" + literal.replace(/'/g, "''") + "'"; } convertTypeName(typeName, size) { switch (typeName) { case 'bpchar': if (size != null) { return 'char'; } // return `pg_catalog.bpchar` below so that the following is symmetric // SELECT char 'c' = char 'c' AS true return 'pg_catalog.bpchar'; case 'varchar': return 'varchar'; case 'numeric': return 'numeric'; case 'bool': return 'boolean'; case 'int2': return 'smallint'; case 'int4': return 'int'; case 'int8': return 'bigint'; case 'real': case 'float4': return 'real'; case 'float8': return 'pg_catalog.float8'; case 'text': // SELECT EXTRACT(CENTURY FROM CURRENT_DATE)>=21 AS True return 'pg_catalog.text'; case 'date': return 'pg_catalog.date'; case 'time': return 'time'; case 'timetz': return 'pg_catalog.timetz'; case 'timestamp': return 'timestamp'; case 'timestamptz': return 'pg_catalog.timestamptz'; case 'interval': return 'interval'; case 'bit': return 'bit'; default: throw new Error(format('Unhandled data type: %s', typeName)); } } type(names, args) { const [ catalog, type ] = names.map(name => this.deparse(name)); const mods = (name, size) => { if (size != null) { return name + '(' + size + ')'; } return name; }; // handle the special "char" (in quotes) type if (names[0].String.str === 'char') { names[0].String.str = '"char"'; } if (catalog !== 'pg_catalog') { return mods(this.list(names, '.'), args); } const res = this.convertTypeName(type, args); return mods(res, args); } deparse(item, context) { if (item == null) { return null; } if (_.isNumber(item)) { return item; } const type = _.keys(item)[0]; const node = _.values(item)[0]; if (this[type] == null) { throw new Error(type + ' is not implemented'); } return this[type](node, context); } ['A_Expr'](node, context) { const output = []; switch (node.kind) { case 0: // AEXPR_OP if (node.lexpr) { output.push(parens(this.deparse(node.lexpr))); } if (node.name.length > 1) { const schema = this.deparse(node.name[0]); const operator = this.deparse(node.name[1]); output.push(`OPERATOR(${schema}.${operator})`); } else { output.push(this.deparse(node.name[0])); } if (node.rexpr) { output.push(parens(this.deparse(node.rexpr))); } if (output.length === 2) { return parens(output.join('')); } return parens(output.join(' ')); case 1: // AEXPR_OP_ANY output.push(this.deparse(node.lexpr)); output.push(format('ANY (%s)', this.deparse(node.rexpr))); return output.join(` ${this.deparse(node.name[0])} `); case 2: // AEXPR_OP_ALL output.push(this.deparse(node.lexpr)); output.push(format('ALL (%s)', this.deparse(node.rexpr))); return output.join(` ${this.deparse(node.name[0])} `); case 3: // AEXPR_DISTINCT return format('%s IS DISTINCT FROM %s', this.deparse(node.lexpr), this.deparse(node.rexpr)); case 4: // AEXPR_NULLIF return format('NULLIF(%s, %s)', this.deparse(node.lexpr), this.deparse(node.rexpr)); case 5: { // AEXPR_OF const op = node.name[0].String.str === '=' ? 'IS OF' : 'IS NOT OF'; return format('%s %s (%s)', this.deparse(node.lexpr), op, this.list(node.rexpr)); } case 6: { // AEXPR_IN const operator = node.name[0].String.str === '=' ? 'IN' : 'NOT IN'; return format('%s %s (%s)', this.deparse(node.lexpr), operator, this.list(node.rexpr)); } case 7: // AEXPR_LIKE output.push(this.deparse(node.lexpr)); if (node.name[0].String.str === '!~~') { output.push(format('NOT LIKE (%s)', this.deparse(node.rexpr))); } else { output.push(format('LIKE (%s)', this.deparse(node.rexpr))); } return output.join(' '); case 8: // AEXPR_ILIKE output.push(this.deparse(node.lexpr)); if (node.name[0].String.str === '!~~*') { output.push(format('NOT ILIKE (%s)', this.deparse(node.rexpr))); } else { output.push(format('ILIKE (%s)', this.deparse(node.rexpr))); } return output.join(' '); case 9: // AEXPR_SIMILAR // SIMILAR TO emits a similar_escape FuncCall node with the first argument output.push(this.deparse(node.lexpr)); if (this.deparse(node.rexpr.FuncCall.args[1].Null)) { output.push(format('SIMILAR TO %s', this.deparse(node.rexpr.FuncCall.args[0]))); } else { output.push(format('SIMILAR TO %s ESCAPE %s', this.deparse(node.rexpr.FuncCall.args[0]), this.deparse(node.rexpr.FuncCall.args[1]))); } return output.join(' '); case 10: // AEXPR_BETWEEN TODO(zhm) untested output.push(this.deparse(node.lexpr)); output.push(format('BETWEEN %s AND %s', this.deparse(node.rexpr[0]), this.deparse(node.rexpr[1]))); return output.join(' '); case 11: // AEXPR_NOT_BETWEEN TODO(zhm) untested output.push(this.deparse(node.lexpr)); output.push(format('NOT BETWEEN %s AND %s', this.deparse(node.rexpr[0]), this.deparse(node.rexpr[1]))); return output.join(' '); default: return fail('A_Expr', node); } } ['Alias'](node, context) { const name = node.aliasname; const output = [ 'AS' ]; if (node.colnames) { output.push(name + parens(this.list(node.colnames))); } else { output.push(this.quote(name)); } return output.join(' '); } ['A_ArrayExpr'](node) { return format('ARRAY[%s]', this.list(node.elements)); } ['A_Const'](node, context) { if (node.val.String) { return this.escape(this.deparse(node.val, context)); } return this.deparse(node.val, context); } ['A_Indices'](node) { if (node.lidx) { return format('[%s:%s]', this.deparse(node.lidx), this.deparse(node.uidx)); } return format('[%s]', this.deparse(node.uidx)); } ['A_Indirection'](node) { const output = [ `(${this.deparse(node.arg)})` ]; // TODO(zhm) figure out the actual rules for when a '.' is needed // // select a.b[0] from a; // select (select row(1)).* // select c2[2].f2 from comptable // select c2.a[2].f2[1].f3[0].a1 from comptable for (let i = 0; i < node.indirection.length; i++) { const subnode = node.indirection[i]; if (subnode.String || subnode.A_Star) { const value = subnode.A_Star ? '*' : this.quote(subnode.String.str); output.push(`.${value}`); } else { output.push(this.deparse(subnode)); } } return output.join(''); } ['A_Star'](node, context) { return '*'; } ['BitString'](node) { const prefix = node.str[0]; return `${prefix}'${node.str.substring(1)}'`; } ['BoolExpr'](node) { switch (node.boolop) { case 0: return parens(this.list(node.args, ' AND ')); case 1: return parens(this.list(node.args, ' OR ')); case 2: return format('NOT (%s)', this.deparse(node.args[0])); default: return fail('BoolExpr', node); } } ['BooleanTest'](node) { const output = []; output.push(this.deparse(node.arg)); const tests = [ 'IS TRUE', 'IS NOT TRUE', 'IS FALSE', 'IS NOT FALSE', 'IS UNKNOWN', 'IS NOT UNKNOWN' ]; output.push(tests[node.booltesttype]); return output.join(' '); } ['CaseExpr'](node) { const output = [ 'CASE' ]; if (node.arg) { output.push(this.deparse(node.arg)); } for (let i = 0; i < node.args.length; i++) { output.push(this.deparse(node.args[i])); } if (node.defresult) { output.push('ELSE'); output.push(this.deparse(node.defresult)); } output.push('END'); return output.join(' '); } ['CoalesceExpr'](node) { return format('COALESCE(%s)', this.list(node.args)); } ['CollateClause'](node) { const output = []; if (node.arg) { output.push(this.deparse(node.arg)); } output.push('COLLATE'); if (node.collname) { output.push(this.quote(this.deparseNodes(node.collname)).join(', ')); } return output.join(' '); } ['ColumnDef'](node) { const output = [ this.quote(node.colname) ]; output.push(this.deparse(node.typeName)); if (node.raw_default) { output.push('USING'); output.push(this.deparse(node.raw_default)); } if (node.constraints) { output.push(this.list(node.constraints, ' ')); } return _.compact(output).join(' '); } ['ColumnRef'](node) { const fields = node.fields.map(field => { if (field.String) { return this.quote(this.deparse(field)); } return this.deparse(field); }); return fields.join('.'); } ['CommonTableExpr'](node) { const output = []; output.push(node.ctename); if (node.aliascolnames) { output.push(format('(%s)', this.quote(this.deparseNodes(node.aliascolnames)).join(', '))); } output.push(format('AS (%s)', this.deparse(node.ctequery))); return output.join(' '); } ['DefElem'](node) { if (node.defname === 'transaction_isolation') { return format('ISOLATION LEVEL %s', node.arg.A_Const.val.String.str.toUpperCase()); } if (node.defname === 'transaction_read_only') { return node.arg.A_Const.val.Integer.ival === 0 ? 'READ WRITE' : 'READ ONLY'; } if (node.defname === 'transaction_deferrable') { return node.arg.A_Const.val.Integer.ival === 0 ? 'NOT DEFERRABLE' : 'DEFERRABLE'; } } ['Float'](node) { // wrap negative numbers in parens, SELECT (-2147483648)::int4 * (-1)::int4 if (node.str[0] === '-') { return `(${node.str})`; } return node.str; } ['FuncCall'](node, context) { const output = []; let params = []; if (node.args) { params = node.args.map(item => { return this.deparse(item); }); } // COUNT(*) if (node.agg_star) { params.push('*'); } const name = this.list(node.funcname, '.'); const order = []; const withinGroup = node.agg_within_group; if (node.agg_order) { order.push('ORDER BY'); order.push(this.list(node.agg_order, ', ')); } const call = []; call.push(name + '('); if (node.agg_distinct) { call.push('DISTINCT '); } // prepend variadic before the last parameter // SELECT CONCAT('|', VARIADIC ARRAY['1','2','3']) if (node.func_variadic) { params[params.length - 1] = 'VARIADIC ' + params[params.length - 1]; } call.push(params.join(', ')); if (order.length && !withinGroup) { call.push(' '); call.push(order.join(' ')); } call.push(')'); output.push(compact(call).join('')); if (order.length && withinGroup) { output.push('WITHIN GROUP'); output.push(parens(order.join(' '))); } if (node.agg_filter != null) { output.push(format('FILTER (WHERE %s)', this.deparse(node.agg_filter))); } if (node.over != null) { output.push(format('OVER %s', this.deparse(node.over))); } return output.join(' '); } ['GroupingFunc'](node) { return 'GROUPING(' + this.list(node.args) + ')'; } ['GroupingSet'](node) { switch (node.kind) { case 0: // GROUPING_SET_EMPTY return '()'; case 1: // GROUPING_SET_SIMPLE return fail('GroupingSet', node); case 2: // GROUPING_SET_ROLLUP return 'ROLLUP (' + this.list(node.content) + ')'; case 3: // GROUPING_SET_CUBE return 'CUBE (' + this.list(node.content) + ')'; case 4: // GROUPING_SET_SETS return 'GROUPING SETS (' + this.list(node.content) + ')'; default: return fail('GroupingSet', node); } } ['Integer'](node, context) { if (node.ival < 0 && context !== 'simple') { return `(${node.ival})`; } return node.ival.toString(); } ['IntoClause'](node) { return this.deparse(node.rel); } ['JoinExpr'](node, context) { const output = []; output.push(this.deparse(node.larg)); if (node.isNatural) { output.push('NATURAL'); } let join = null; switch (true) { case node.jointype === 0 && (node.quals != null): join = 'INNER JOIN'; break; case node.jointype === 0 && !node.isNatural && !(node.quals != null) && !(node.usingClause != null): join = 'CROSS JOIN'; break; case node.jointype === 0: join = 'JOIN'; break; case node.jointype === 1: join = 'LEFT OUTER JOIN'; break; case node.jointype === 2: join = 'FULL OUTER JOIN'; break; case node.jointype === 3: join = 'RIGHT OUTER JOIN'; break; default: fail('JoinExpr', node); break; } output.push(join); if (node.rarg) { // wrap nested join expressions in parens to make the following symmetric: // select * from int8_tbl x cross join (int4_tbl x cross join lateral (select x.f1) ss) if ((node.rarg.JoinExpr != null) && !(node.rarg.JoinExpr.alias != null)) { output.push(`(${this.deparse(node.rarg)})`); } else { output.push(this.deparse(node.rarg)); } } if (node.quals) { output.push(`ON ${this.deparse(node.quals)}`); } if (node.usingClause) { const using = this.quote(this.deparseNodes(node.usingClause)).join(', '); output.push(`USING (${using})`); } const wrapped = (node.rarg.JoinExpr != null) || node.alias ? '(' + output.join(' ') + ')' : output.join(' '); if (node.alias) { return wrapped + ' ' + this.deparse(node.alias); } return wrapped; } ['LockingClause'](node) { const strengths = [ 'NONE', // LCS_NONE 'FOR KEY SHARE', 'FOR SHARE', 'FOR NO KEY UPDATE', 'FOR UPDATE' ]; const output = []; output.push(strengths[node.strength]); if (node.lockedRels) { output.push('OF'); output.push(this.list(node.lockedRels)); } return output.join(' '); } ['MinMaxExpr'](node) { const output = []; if (node.op === 0) { output.push('GREATEST'); } else { output.push('LEAST'); } output.push(parens(this.list(node.args))); return output.join(''); } ['NamedArgExpr'](node) { const output = []; output.push(node.name); output.push(':='); output.push(this.deparse(node.arg)); return output.join(' '); } ['Null'](node) { return 'NULL'; } ['NullTest'](node) { const output = [ this.deparse(node.arg) ]; if (node.nulltesttype === 0) { output.push('IS NULL'); } else if (node.nulltesttype === 1) { output.push('IS NOT NULL'); } return output.join(' '); } ['ParamRef'](node) { if (node.number >= 0) { return [ '$', node.number ].join(''); } return '?'; } ['RangeFunction'](node) { const output = []; if (node.lateral) { output.push('LATERAL'); } const funcs = []; for (let i = 0; i < node.functions.length; i++) { const funcCall = node.functions[i]; const call = [ this.deparse(funcCall[0]) ]; if (funcCall[1] && funcCall[1].length) { call.push(format('AS (%s)', this.list(funcCall[1]))); } funcs.push(call.join(' ')); } const calls = funcs.join(', '); if (node.is_rowsfrom) { output.push(`ROWS FROM (${calls})`); } else { output.push(calls); } if (node.ordinality) { output.push('WITH ORDINALITY'); } if (node.alias) { output.push(this.deparse(node.alias)); } if (node.coldeflist) { const defList = this.list(node.coldeflist); if (!node.alias) { output.push(` AS (${defList})`); } else { output.push(`(${defList})`); } } return output.join(' '); } ['RangeSubselect'](node, context) { let output = ''; if (node.lateral) { output += 'LATERAL '; } output += parens(this.deparse(node.subquery)); if (node.alias) { return output + ' ' + this.deparse(node.alias); } return output; } ['RangeTableSample'](node) { const output = []; output.push(this.deparse(node.relation)); output.push('TABLESAMPLE'); output.push(this.deparse(node.method[0])); if (node.args) { output.push(parens(this.list(node.args))); } if (node.repeatable) { output.push('REPEATABLE(' + this.deparse(node.repeatable) + ')'); } return output.join(' '); } ['RangeVar'](node, context) { const output = []; if (node.inhOpt === 0) { output.push('ONLY'); } if (node.relpersistence === 'u') { output.push('UNLOGGED'); } if (node.relpersistence === 't') { output.push('TEMPORARY'); } if (node.schemaname != null) { output.push(this.quote(node.schemaname)); output.push('.'); } output.push(this.quote(node.relname)); if (node.alias) { output.push(this.deparse(node.alias)); } return output.join(' '); } ['ResTarget'](node, context) { if (context === 'select') { return compact([ this.deparse(node.val), this.quote(node.name) ]).join(' AS '); } else if (context === 'update') { return compact([ node.name, this.deparse(node.val) ]).join(' = '); } else if (!(node.val != null)) { return this.quote(node.name); } return fail('ResTarget', node); } ['RowExpr'](node) { if (node.row_format === 2) { return parens(this.list(node.args)); } return format('ROW(%s)', this.list(node.args)); } ['SelectStmt'](node, context) { const output = []; if (node.withClause) { output.push(this.deparse(node.withClause)); } if (node.op === 0) { // VALUES select's don't get SELECT if (node.valuesLists == null) { output.push('SELECT'); } } else { output.push(parens(this.deparse(node.larg))); const sets = [ 'NONE', 'UNION', 'INTERSECT', 'EXCEPT' ]; output.push(sets[node.op]); if (node.all) { output.push('ALL'); } output.push(parens(this.deparse(node.rarg))); } if (node.distinctClause) { if (node.distinctClause[0] != null) { output.push('DISTINCT ON'); const clause = (node.distinctClause.map(e => this.deparse(e, 'select'))).join(',\n'); output.push(`(${clause})`); } else { output.push('DISTINCT'); } } if (node.targetList) { output.push(indent((node.targetList.map(e => this.deparse(e, 'select'))).join(',\n'))); } if (node.intoClause) { output.push('INTO'); output.push(indent(this.deparse(node.intoClause))); } if (node.fromClause) { output.push('FROM'); output.push(indent((node.fromClause.map(e => this.deparse(e, 'from'))).join(',\n'))); } if (node.whereClause) { output.push('WHERE'); output.push(indent(this.deparse(node.whereClause))); } if (node.valuesLists) { output.push('VALUES'); const lists = node.valuesLists.map(list => { return `(${(list.map(v => this.deparse(v))).join(', ')})`; }); output.push(lists.join(', ')); } if (node.groupClause) { output.push('GROUP BY'); output.push(indent((node.groupClause.map(e => this.deparse(e, 'group'))).join(',\n'))); } if (node.havingClause) { output.push('HAVING'); output.push(indent(this.deparse(node.havingClause))); } if (node.windowClause) { output.push('WINDOW'); const windows = []; for (let i = 0; i < node.windowClause.length; i++) { const w = node.windowClause[i]; const window = []; if (w.WindowDef.name) { window.push(this.quote(w.WindowDef.name) + ' AS'); } window.push(parens(this.deparse(w, 'window'))); windows.push(window.join(' ')); } output.push(windows.join(', ')); } if (node.sortClause) { output.push('ORDER BY'); output.push(indent((node.sortClause.map(e => this.deparse(e, 'sort'))).join(',\n'))); } if (node.limitCount) { output.push('LIMIT'); output.push(indent(this.deparse(node.limitCount))); } if (node.limitOffset) { output.push('OFFSET'); output.push(indent(this.deparse(node.limitOffset))); } if (node.lockingClause) { node.lockingClause.forEach(item => { return output.push(this.deparse(item)); }); } return output.join(' '); } ['SortBy'](node) { const output = []; output.push(this.deparse(node.node)); if (node.sortby_dir === 1) { output.push('ASC'); } if (node.sortby_dir === 2) { output.push('DESC'); } if (node.sortby_dir === 3) { output.push(`USING ${this.deparseNodes(node.useOp)}`); } if (node.sortby_nulls === 1) { output.push('NULLS FIRST'); } if (node.sortby_nulls === 2) { output.push('NULLS LAST'); } return output.join(' '); } ['String'](node) { return node.str; } ['SubLink'](node) { switch (true) { case node.subLinkType === 0: return format('EXISTS (%s)', this.deparse(node.subselect)); case node.subLinkType === 1: return format('%s %s ALL (%s)', this.deparse(node.testexpr), this.deparse(node.operName[0]), this.deparse(node.subselect)); case node.subLinkType === 2 && !(node.operName != null): return format('%s IN (%s)', this.deparse(node.testexpr), this.deparse(node.subselect)); case node.subLinkType === 2: return format('%s %s ANY (%s)', this.deparse(node.testexpr), this.deparse(node.operName[0]), this.deparse(node.subselect)); case node.subLinkType === 3: return format('%s %s (%s)', this.deparse(node.testexpr), this.deparse(node.operName[0]), this.deparse(node.subselect)); case node.subLinkType === 4: return format('(%s)', this.deparse(node.subselect)); case node.subLinkType === 5: // TODO(zhm) what is this? return fail('SubLink', node); // MULTIEXPR_SUBLINK // format('(%s)', @deparse(node.subselect)) case node.subLinkType === 6: return format('ARRAY (%s)', this.deparse(node.subselect)); default: return fail('SubLink', node); } } ['TypeCast'](node) { return this.deparse(node.arg) + '::' + this.deparse(node.typeName); } ['TypeName'](node) { if (_.last(node.names).String.str === 'interval') { return this.deparseInterval(node); } const output = []; if (node.setof) { output.push('SETOF'); } let args = null; if (node.typmods != null) { args = node.typmods.map(item => { return this.deparse(item); }); } const type = []; type.push(this.type(node.names, args && args.join(', '))); if (node.arrayBounds != null) { type.push('[]'); } output.push(type.join('')); return output.join(' '); } ['CaseWhen'](node) { const output = [ 'WHEN' ]; output.push(this.deparse(node.expr)); output.push('THEN'); output.push(this.deparse(node.result)); return output.join(' '); } ['VariableSetStmt'](node) { if (node.kind === 4) { return format('RESET %s', node.name); } if (node.kind === 3) { const name = { 'TRANSACTION': 'TRANSACTION', 'SESSION CHARACTERISTICS': 'SESSION CHARACTERISTICS AS TRANSACTION' }[node.name]; return format('SET %s %s', name, this.deparseNodes(node.args, 'simple').join(', ')); } if (node.kind === 1) { return format('SET %s TO DEFAULT', node.name); } return format('SET %s%s = %s', node.is_local ? 'LOCAL ' : '', node.name, this.deparseNodes(node.args, 'simple').join(', ')); } ['VariableShowStmt'](node) { return format('SHOW %s', node.name); } ['WindowDef'](node, context) { const output = []; if (context !== 'window') { if (node.name) { output.push(node.name); } } const empty = (!(node.partitionClause != null) && !(node.orderClause != null)); const frameOptions = this.deparseFrameOptions(node.frameOptions, node.refname, node.startOffset, node.endOffset); if (empty && context !== 'window' && !(node.name != null) && frameOptions.length === 0) { return '()'; } const windowParts = []; let useParens = false; if (node.partitionClause) { const partition = [ 'PARTITION BY' ]; const clause = node.partitionClause.map(item => this.deparse(item)); partition.push(clause.join(', ')); windowParts.push(partition.join(' ')); useParens = true; } if (node.orderClause) { windowParts.push('ORDER BY'); const orders = node.orderClause.map(item => { return this.deparse(item); }); windowParts.push(orders.join(', ')); useParens = true; } if (frameOptions.length) { useParens = true; windowParts.push(frameOptions); } if (useParens && context !== 'window') { return output.join(' ') + ' (' + windowParts.join(' ') + ')'; } return output.join(' ') + windowParts.join(' '); } ['WithClause'](node) { const output = [ 'WITH' ]; if (node.recursive) { output.push('RECURSIVE'); } output.push(this.list(node.ctes)); return output.join(' '); } deparseFrameOptions(options, refName, startOffset, endOffset) { const FRAMEOPTION_NONDEFAULT = 0x00001; // any specified? const FRAMEOPTION_RANGE = 0x00002; // RANGE behavior const FRAMEOPTION_ROWS = 0x00004; // ROWS behavior const FRAMEOPTION_BETWEEN = 0x00008; // BETWEEN given? const FRAMEOPTION_START_UNBOUNDED_PRECEDING = 0x00010; // start is U. P. const FRAMEOPTION_END_UNBOUNDED_PRECEDING = 0x00020; // (disallowed) const FRAMEOPTION_START_UNBOUNDED_FOLLOWING = 0x00040; // (disallowed) const FRAMEOPTION_END_UNBOUNDED_FOLLOWING = 0x00080; // end is U. F. const FRAMEOPTION_START_CURRENT_ROW = 0x00100; // start is C. R. const FRAMEOPTION_END_CURRENT_ROW = 0x00200; // end is C. R. const FRAMEOPTION_START_VALUE_PRECEDING = 0x00400; // start is V. P. const FRAMEOPTION_END_VALUE_PRECEDING = 0x00800; // end is V. P. const FRAMEOPTION_START_VALUE_FOLLOWING = 0x01000; // start is V. F. const FRAMEOPTION_END_VALUE_FOLLOWING = 0x02000; // end is V. F. if (!(options & FRAMEOPTION_NONDEFAULT)) { return ''; } const output = []; if (refName != null) { output.push(refName); } if (options & FRAMEOPTION_RANGE) { output.push('RANGE'); } if (options & FRAMEOPTION_ROWS) { output.push('ROWS'); } const between = options & FRAMEOPTION_BETWEEN; if (between) { output.push('BETWEEN'); } if (options & FRAMEOPTION_START_UNBOUNDED_PRECEDING) { output.push('UNBOUNDED PRECEDING'); } if (options & FRAMEOPTION_START_UNBOUNDED_FOLLOWING) { output.push('UNBOUNDED FOLLOWING'); } if (options & FRAMEOPTION_START_CURRENT_ROW) { output.push('CURRENT ROW'); } if (options & FRAMEOPTION_START_VALUE_PRECEDING) { output.push(this.deparse(startOffset) + ' PRECEDING'); } if (options & FRAMEOPTION_START_VALUE_FOLLOWING) { output.push(this.deparse(startOffset) + ' FOLLOWING'); } if (between) { output.push('AND'); if (options & FRAMEOPTION_END_UNBOUNDED_PRECEDING) { output.push('UNBOUNDED PRECEDING'); } if (options & FRAMEOPTION_END_UNBOUNDED_FOLLOWING) { output.push('UNBOUNDED FOLLOWING'); } if (options & FRAMEOPTION_END_CURRENT_ROW) { output.push('CURRENT ROW'); } if (options & FRAMEOPTION_END_VALUE_PRECEDING) { output.push(this.deparse(endOffset) + ' PRECEDING'); } if (options & FRAMEOPTION_END_VALUE_FOLLOWING) { output.push(this.deparse(endOffset) + ' FOLLOWING'); } } return output.join(' '); } deparseInterval(node) { const type = [ 'interval' ]; if (node.arrayBounds != null) { type.push('[]'); } if (node.typmods) { const typmods = node.typmods.map(item => this.deparse(item)); let intervals = this.interval(typmods[0]); // SELECT interval(0) '1 day 01:23:45.6789' if (node.typmods[0] && node.typmods[0].A_Const && node.typmods[0].A_Const.val.Integer.ival === 32767 && node.typmods[1] && (node.typmods[1].A_Const != null)) { intervals = [ `(${node.typmods[1].A_Const.val.Integer.ival})` ]; } else { intervals = intervals.map(part => { if (part === 'second' && typmods.length === 2) { return 'second(' + _.last(typmods) + ')'; } return part; }); } type.push(intervals.join(' to ')); } return type.join(' '); } interval(mask) { // ported from https://github.com/lfittl/pg_query/blob/master/lib/pg_query/deparse/interval.rb if (this.MASKS == null) { this.MASKS = { 0: 'RESERV', 1: 'MONTH', 2: 'YEAR', 3: 'DAY', 4: 'JULIAN', 5: 'TZ', 6: 'DTZ', 7: 'DYNTZ', 8: 'IGNORE_DTF', 9: 'AMPM', 10: 'HOUR', 11: 'MINUTE', 12: 'SECOND', 13: 'MILLISECOND', 14: 'MICROSECOND', 15: 'DOY', 16: 'DOW', 17: 'UNITS', 18: 'ADBC', 19: 'AGO', 20: 'ABS_BEFORE', 21: 'ABS_AFTER', 22: 'ISODATE', 23: 'ISOTIME', 24: 'WEEK', 25: 'DECADE', 26: 'CENTURY', 27: 'MILLENNIUM', 28: 'DTZMOD' }; } if (this.BITS == null) { this.BITS = _.invert(this.MASKS); } if (this.INTERVALS == null) { this.INTERVALS = {}; this.INTERVALS[(1 << this.BITS.YEAR)] = [ 'year' ]; this.INTERVALS[(1 << this.BITS.MONTH)] = [ 'month' ]; this.INTERVALS[(1 << this.BITS.DAY)] = [ 'day' ]; this.INTERVALS[(1 << this.BITS.HOUR)] = [ 'hour' ]; this.INTERVALS[(1 << this.BITS.MINUTE)] = [ 'minute' ]; this.INTERVALS[(1 << this.BITS.SECOND)] = [ 'second' ]; this.INTERVALS[(1 << this.BITS.YEAR | 1 << this.BITS.MONTH)] = [ 'year', 'month' ]; this.INTERVALS[(1 << this.BITS.DAY | 1 << this.BITS.HOUR)] = [ 'day', 'hour' ]; this.INTERVALS[(1 << this.BITS.DAY | 1 << this.BITS.HOUR | 1 << this.BITS.MINUTE)] = [ 'day', 'minute' ]; this.INTERVALS[(1 << this.BITS.DAY | 1 << this.BITS.HOUR | 1 << this.BITS.MINUTE | 1 << this.BITS.SECOND)] = [ 'day', 'second' ]; this.INTERVALS[(1 << this.BITS.HOUR | 1 << this.BITS.MINUTE)] = [ 'hour', 'minute' ]; this.INTERVALS[(1 << this.BITS.HOUR | 1 << this.BITS.MINUTE | 1 << this.BITS.SECOND)] = [ 'hour', 'second' ]; this.INTERVALS[(1 << this.BITS.MINUTE | 1 << this.BITS.SECOND)] = [ 'minute', 'second' ]; // utils/timestamp.h // #define INTERVAL_FULL_RANGE (0x7FFF) this.INTERVALS[this.INTERVAL_FULL_RANGE = '32767'] = []; } return this.INTERVALS[mask.toString()]; } } module.exports = Deparser;