arangodb-cubejs-driver
Version:
Cube.js arangodb driver
248 lines • 9.77 kB
JavaScript
;
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