@aggregion/mongo-like-query-to-sql
Version:
Mongo-like to SQL query converter
298 lines (297 loc) • 11 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.objToSQL = exports.convertASTToSQL = exports.parseObjToAST = void 0;
/* eslint-disable @typescript-eslint/no-use-before-define */
var isEmpty = function (arg) {
if (arg === null || arg === undefined) {
return true;
}
if (typeof arg === 'string') {
return !arg.length;
}
if (Array.isArray(arg)) {
return !arg.length;
}
if (typeof arg === 'object') {
return !Object.keys(arg).length;
}
return false;
};
var castValue = function (v) {
if (typeof v === 'number') {
return v;
}
if (typeof v === 'string') {
return "'" + v + "'";
}
if (v === null) {
return 'NULL';
}
return v;
};
var isOperator = function (v) { return v.startsWith('$'); };
// https://clickhouse.yandex/docs/ru/query_language/functions/array_functions/
var isFunction = function (v) { return ['$has', '$hasAll', '$hasAny', '$notHasAll', '$notHasAny', '$notHas'].includes(v); };
var isScalar = function (v) { return ['number', 'string'].includes(typeof v) || v === null; };
var castOp = function (v) {
switch (v) {
case '$and':
return 'AND';
case '$or':
return 'OR';
case '$lt':
return '<';
case '$gt':
return '>';
case '$lte':
return '<=';
case '$gte':
return '>=';
case '$eq':
return '=';
case '$ne':
return '!=';
case '$in':
return 'IN';
case '$nin':
return 'NOT IN';
case '$not':
return 'NOT';
case '$is':
return 'IS';
default:
throw new Error("Unknown operator " + v);
}
};
exports.parseObjToAST = function (_a) {
var _b;
var where = _a.where, parentField = _a.parentField, parentOp = _a.parentOp, _c = _a.parentIsArray, parentIsArray = _c === void 0 ? false : _c;
if (isScalar(where) || (parentOp && ['$in', '$nin'].includes(parentOp))) {
return { ast: where };
}
var ast = [];
var foreigns = {};
for (var field in where) {
if (isOperator(field)) {
if (isFunction(field)) {
// const parsedAst = parseObjToAST({where: where[field], parentField: field, parentOp: field});
// if (parsedAst.foreigns) {
// Object.assign(foreigns, parsedAst.foreigns);
// }
var tmp = {
value: where[field],
func: field.substring(1),
};
if (parentField) {
tmp.field = parentField;
}
ast.push(tmp);
}
else if (['$inSubquery', '$ninSubquery'].includes(field)) {
var subquery = where[field];
if ('having' in subquery && typeof subquery.having !== 'string') {
throw new Error(field + " having clause should be of type string");
}
var parsedAST = exports.parseObjToAST({ where: subquery.where });
var tmp = (_b = {},
_b[field.substr(1)] = {
whereAST: parsedAST.ast,
from: subquery.from,
field: subquery.field,
foreigns: parsedAST.foreigns,
groupBy: subquery.groupBy,
having: subquery.having,
},
_b.field = parentField,
_b);
ast.push(tmp);
}
else {
var parsedAst = exports.parseObjToAST({
where: where[field],
parentField: field,
parentOp: field,
});
if (parsedAst.foreigns) {
Object.assign(foreigns, parsedAst.foreigns);
}
var tmp = {
value: parsedAst.ast,
op: castOp(field),
};
if (parentField && !parentIsArray) {
tmp.field = parentField;
}
if (tmp.field && tmp.field.indexOf('.') > 0) {
foreigns[tmp.field.split('.')[0]] = true;
}
ast.push(tmp);
}
}
else {
var parsedAst = exports.parseObjToAST({
where: where[field],
parentField: field,
parentOp: parentOp,
parentIsArray: Array.isArray(where),
});
if (parsedAst.foreigns) {
Object.assign(foreigns, parsedAst.foreigns);
}
if (isScalar(parsedAst.ast)) {
return {
ast: {
field: field,
value: parsedAst.ast,
op: castOp('$eq'),
},
foreigns: foreigns,
};
}
ast.push({
values: parsedAst.ast,
op: 'AND',
});
}
}
return { ast: ast, foreigns: foreigns };
};
// eslint-disable-next-line complexity
var castToConditionPiece = function (_a) {
var _b = _a === void 0 ? {} : _a, op = _b.op, field = _b.field, inSubquery = _b.inSubquery, ninSubquery = _b.ninSubquery, value = _b.value, func = _b.func;
if (op) {
switch (op) {
case 'IN':
case 'NOT IN':
if (!Array.isArray(value)) {
throw new TypeError('Incorrect type of value for "IN". Value must be an Array.');
}
return field + " " + op + " (" + value.map(castValue).join(', ') + ")";
case 'OR':
case 'AND':
if (!Array.isArray(value)) {
throw new TypeError("Incorrect type of value for \"" + op + "\". Value must be an Array.");
}
return "(" + value.map(exports.convertASTToSQL).join(" " + op + " ") + ")";
case 'NOT':
if (Array.isArray(value)) {
return "NOT (" + value.map(exports.convertASTToSQL) + ")";
}
if (isScalar(value)) {
return "NOT " + castValue(value);
}
return "NOT (" + castToConditionPiece(value) + ")"; // Maybe incorrect. Not used.
case 'IS':
if (isScalar(value)) {
return field + " IS " + castValue(value);
}
return field + " IS " + value.map(castToConditionPiece);
default:
return field + " " + op + " " + castValue(value);
}
}
else if (func) {
switch (func) {
case 'hasAny':
case 'hasAll':
if (Array.isArray(value)) {
return func + "(" + field + ", [" + value.map(castValue).join(', ') + "])";
}
if (isScalar(value)) {
return func + "(" + field + ", [" + castValue(value) + "])";
}
throw new Error("Incorrect argument for \"" + func + "\" func");
case 'notHasAll':
if (Array.isArray(value)) {
return "NOT hasAll(" + field + ", [" + value.map(castValue).join(', ') + "])";
}
if (isScalar(value)) {
return "NOT hasAll(" + field + ", [" + castValue(value) + "])";
}
throw new Error("Incorrect argument for \"" + func + "\" func");
case 'notHasAny':
if (Array.isArray(value)) {
return "NOT hasAny(" + field + ", [" + value.map(castValue).join(', ') + "])";
}
if (isScalar(value)) {
return "NOT hasAny(" + field + ", [" + castValue(value) + "])";
}
throw new Error("Incorrect argument for \"" + func + "\" func");
case 'has':
if (!isScalar(value)) {
throw new Error("Incorrect argument for \"" + func + "\" func");
}
return func + "(" + field + ", " + castValue(value) + ")";
case 'notHas':
if (!isScalar(value)) {
throw new Error("Incorrect argument for \"" + func + "\" func");
}
return "NOT has(" + field + ", " + castValue(value) + ")";
default:
throw new Error('Unknown func');
}
}
else if (inSubquery) {
var sql = field + " IN (SELECT " + inSubquery.field + " FROM " + inSubquery.from;
if (inSubquery.whereAST && !isEmpty(inSubquery.whereAST)) {
sql += " WHERE " + exports.convertASTToSQL(inSubquery.whereAST);
}
if (inSubquery.groupBy) {
sql += " GROUP BY " + inSubquery.groupBy;
}
if (inSubquery.having) {
sql += " HAVING " + inSubquery.having;
}
sql += ')';
return sql;
}
else if (ninSubquery) {
var sql = field + " NOT IN (SELECT " + ninSubquery.field + " FROM " + ninSubquery.from;
if (ninSubquery.whereAST && !isEmpty(ninSubquery.whereAST)) {
sql += " WHERE " + exports.convertASTToSQL(ninSubquery.whereAST);
}
if (ninSubquery.groupBy) {
sql += " GROUP BY " + ninSubquery.groupBy;
}
if (ninSubquery.having) {
sql += " HAVING " + ninSubquery.having;
}
sql += ')';
return sql;
}
else {
throw new Error('AST item must contain "op" or "func"');
}
};
exports.convertASTToSQL = function (ast) {
var sqlArr = [];
if (Array.isArray(ast)) {
for (var _i = 0, ast_1 = ast; _i < ast_1.length; _i++) {
var value = ast_1[_i];
if (Array.isArray(value.values)) {
sqlArr.push("(" + value.values.map(exports.convertASTToSQL).join(" " + value.op + " ") + ")");
}
else {
sqlArr.push(castToConditionPiece(value));
}
}
}
else if (Array.isArray(ast.values)) {
sqlArr.push("(" + ast.values.map(exports.convertASTToSQL).join(" " + ast.op + " ") + ")");
}
else {
sqlArr.push(castToConditionPiece(ast));
}
return sqlArr.join(' AND ');
};
exports.objToSQL = function (where) {
var result = exports.parseObjToAST({ where: where });
if (result.ast &&
Array.isArray(result.ast) &&
result.ast.length > 0 &&
Array.isArray(result.ast[0].value) &&
result.ast[0].value.length === 0) {
return '(1=1)';
}
return exports.convertASTToSQL(result.ast);
};