UNPKG

arangodb-cubejs-driver

Version:
248 lines 9.77 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.sql2aql = exports.mapProjectStatement = exports.mapLimitStatement = exports.mapOrderByStatement = exports.mapAggrStatement = exports.mapGroupByStatement = exports.mapWhereStatement = exports.mapFromStatment = exports.hasCalculatedColumns = exports.capitalizeFirstLetter = exports.isNumeric = exports.indent = void 0; const pgsql_ast_parser_1 = require("pgsql-ast-parser"); const functionMap = { count: 'COUNT', countDistinct: 'COUNT_DISTINCT', min: 'MIN', max: 'MAX', sum: 'SUM', avg: 'AVG' }; const operatorMap = { '=': '==', 'ILIKE': 'LIKE', 'NOT ILIKE': 'NOT LIKE' }; const indentMap = {}; function indent(level, size = 2) { if (!indentMap[level]) { indentMap[level] = ' '.repeat(level * size); } return indentMap[level]; } exports.indent = indent; function isNumeric(val) { // // See also: https://github.com/angular/angular/blob/c1052cf7a77e0bf2a4ec14f9dd5abc92034cfd2e/packages/common/src/pipes/number_pipe.ts#L289C7-L289C77 // typeof value === 'string' && !isNaN(Number(value) - parseFloat(value)) return !(val instanceof Array) && (val - parseFloat(val) + 1) >= 0; } exports.isNumeric = isNumeric; function capitalizeFirstLetter(string) { return string ? string[0].toUpperCase() + string.slice(1) : ''; } exports.capitalizeFirstLetter = capitalizeFirstLetter; function hasCalculatedColumns(columns) { for (const col of columns) { if (col.expr.type === 'call') { return true; } } return false; } exports.hasCalculatedColumns = hasCalculatedColumns; function mapFromStatment(fromAst, ctx) { if (fromAst.length !== 1) { throw new Error(`Invalid from ast! ${fromAst.length} statement(s)`); } return `FOR ${ctx.docRef} IN ${fromAst[0]['name']['name']}`; } exports.mapFromStatment = mapFromStatment; function mapOpStat(expr, params, ctx, deep = 0) { switch (expr.type) { case 'ref': { return `${ctx.docRef}.${expr.name}`; } case 'parameter': { if (expr.name[0] === '$') { // SQL positional parameter, e.g. {"type":"parameter","name":"$1"} // Remove leading $, convert to numeric (+), adjust to zero-based index (-1). const position = +expr.name.substring(1) - 1; const value = params[position]; if (isNumeric(value)) { return +value; } else if (typeof value === 'string') { return `'${value}'`; } return value; } else { throw Error(`Unsupported parameter ${JSON.stringify(params)}`); } } case 'boolean': case 'integer': return expr.value; case 'string': return `'${expr.value}'`; case 'unary': { // Extract operand value by recursive call to current function. const operand = mapOpStat(expr.operand, params, ctx); switch (expr.op) { case 'IS NULL': return `${operand} == null`; case 'IS NOT NULL': return `${operand} != null`; default: throw Error(`Unsupported operator ${expr.op}!`); } } case 'binary': { // Extract operator value with substitution. const op = operatorMap[expr.op] || expr.op; // Extract operands' values by recursive call to current function. const lhs = mapOpStat(expr.left, params, ctx); const rhs = mapOpStat(expr.right, params, ctx); if (op === '||') { // lhs or rhs is a wildcard literal ('%') to append for searching with LIKE. return `CONCAT(${lhs}, ${rhs})`; } return deep > 0 ? `(${lhs} ${op} ${rhs})` : `${lhs} ${op} ${rhs}`; } default: throw Error(`Unsupported where expr type ${expr.type}!`); } } function mapWhereStatement(whereAst, params, ctx) { let filterStr = mapOpStat(whereAst, params, ctx); if (filterStr) { return `FILTER ${filterStr}`; } throw Error(`Unsupported filter string ${JSON.stringify(whereAst)}`); } exports.mapWhereStatement = mapWhereStatement; function mapGroupByStatement(groupByAsts, columns, ctx) { const collectArr = []; ctx.collectMap = {}; if (groupByAsts) { // Transpile GROUP BY SQL columns to COLLECT and RETURN AQL columns. RETURN columns are passed via collectMap. for (const groupByAst of groupByAsts) { switch (groupByAst.type) { case 'integer': const groupCol = columns[groupByAst.value - 1]; const collectEl = `${groupCol.alias.name} = ${ctx.docRef}.${groupCol.expr['name']}`; ctx.collectMap[groupCol.alias.name] = collectEl; collectArr.push(collectEl); break; default: throw Error(`Unsupported groupBy expr type ${groupByAst.type}!`); } } } // Return one of the following: // - COLLECT followed by columns if there are GROUP BY in SQL. // - COLLECT statement if there are only calculated columns for further representation by AGGREGATE AQL. return `COLLECT ${collectArr.join(',')}`; } exports.mapGroupByStatement = mapGroupByStatement; function mapAggrStatement(columns, ctx) { let aggArr = []; for (const col of columns) { if (col.expr.type === 'call') { let aqlFunc = functionMap[col.expr.function.name + capitalizeFirstLetter(col.expr.distinct)]; if (aqlFunc) { let aggrEl = `${col.alias.name} = ${aqlFunc}(${col.expr.args.map((expr) => `${ctx.docRef}.${expr['name']}`).join(',')})`; ctx.collectMap[col.alias.name] = aggrEl; aggArr.push(aggrEl); } else { throw Error(`AQL mapping is missing for SQL function ${col.expr.function.name}`); } } } if (aggArr.length) { return `AGGREGATE ${aggArr.join(',')}`; } return undefined; } exports.mapAggrStatement = mapAggrStatement; function mapOrderByStatement(orderByAsts, columns, ctx) { const orderByArr = []; for (const orderByAst of orderByAsts) { switch (orderByAst.by.type) { case 'integer': const orderCol = columns[orderByAst.by.value - 1]; const orderByEl = `${orderCol.alias.name} ${orderByAst.order}`; // ctx.collectMap[groupCol.alias.name] = collectEl; orderByArr.push(orderByEl); break; default: throw Error(`Unsupported orderBy by type ${orderByAst.by.type}!`); } } return `SORT ${orderByArr.join(',')}`; } exports.mapOrderByStatement = mapOrderByStatement; function mapLimitStatement(limitAst) { let limitStr = ''; if (limitAst.limit) { switch (limitAst.limit.type) { case 'integer': limitStr = `${limitAst.limit.value}`; break; default: throw Error(`Unsupported limit type ${limitAst.limit.type}!`); } } return `LIMIT ${limitStr}`; } exports.mapLimitStatement = mapLimitStatement; function mapProjectStatement(columns, ctx) { if (columns.length === 1 && columns[0].expr.type === 'ref' && columns[0].expr.name === '*') { return `RETURN ${ctx.docRef}`; } const returnArr = []; for (const col of columns) { if (ctx.collectMap && ctx.collectMap[col.alias.name]) { returnArr.push(`${col.alias.name}`); } else { switch (col.expr.type) { case 'ref': returnArr.push(`${col.alias.name}:${ctx.docRef}.${col.expr.name}`); break; case 'call': // Handled in COLLECT and AGGREGATE AQL transpilers of mapGroupByStatement() and mapAggrStatement(). break; default: throw Error(`Unsupported projection expr type ${col.expr.type}!`); } } } return `RETURN {${returnArr.join(',')}}`; } exports.mapProjectStatement = mapProjectStatement; function sql2aql(sql, params = []) { const ast = (0, pgsql_ast_parser_1.parse)(sql); let aqlQuery = ''; let firstAst = ast[0]; if (firstAst.type === 'select') { let ctx = { docRef: 'doc' }; aqlQuery = `${mapFromStatment(firstAst.from, ctx)}\n`; if (firstAst.where) { let filterStr = mapWhereStatement(firstAst.where, params, ctx); if (filterStr) { aqlQuery += `${indent(1)}${filterStr}\n`; } } if (firstAst.groupBy || hasCalculatedColumns(firstAst.columns)) { aqlQuery += `${indent(1)}${mapGroupByStatement(firstAst.groupBy, firstAst.columns, ctx)}\n`; let aggrStr = mapAggrStatement(firstAst.columns, ctx); if (aggrStr) { aqlQuery += `${indent(1)}${aggrStr}\n`; } } if (firstAst.orderBy) { aqlQuery += `${indent(1)}${mapOrderByStatement(firstAst.orderBy, firstAst.columns, ctx)}\n`; } if (firstAst.limit) { aqlQuery += `${indent(1)}${mapLimitStatement(firstAst.limit)}\n`; } aqlQuery += `${indent(1)}${mapProjectStatement(firstAst.columns, ctx)}`; } return aqlQuery; } exports.sql2aql = sql2aql; //# sourceMappingURL=sql-utils.js.map