UNPKG

odata-sequelize

Version:
405 lines (355 loc) 9.92 kB
const parser = require("odata-parser"); const objectOperators = [ "and", "or", "between", "notBetween", "in", "notIn", "any", "overlap", "contains", "contained" ]; const valueOperators = [ "gt", "gte", "lt", "lte", "ne", "eq", "not", "like", "notLike", "iLike", "notILike", "regexp", "notRegexp", "iRegexp", "notIRegexp", "col" ]; const customOperators = [ "ge", "le", "substringof", "startswith", "endswith", "tolower", "toupper", "trim", "year", "month", "day", "hour", "minute", "second" ]; let mappedValueOperators = []; function mapOperators(sequelize) { mappedValueOperators = valueOperators.map(element => sequelize.Sequelize.Op[element]); sequelize.options.operatorsAliases = mappedValueOperators; } function getOperator(strOperator, sequelize) { if (!sequelize.Sequelize.Op) throw new Error("Sequelize operator not found."); const allOperators = objectOperators.concat(valueOperators).concat(customOperators); if (!allOperators.includes(strOperator)) throw new Error(`Operator not recognized: ${strOperator}`); const selectedOperator = sequelize.Sequelize.Op[strOperator]; if (!selectedOperator) { switch (strOperator) { case "ge": return sequelize.Sequelize.Op.gte; case "le": return sequelize.Sequelize.Op.lte; case "substringof": case "startswith": case "endswith": return sequelize.Sequelize.Op.like; case "tolower": case "toupper": case "trim": case "year": case "month": case "day": case "hour": case "minute": case "second": return sequelize.Sequelize.Op.eq; default: throw new Error("Sequelize operator not found."); } } else return selectedOperator; } function transformTree(root, sequelize) { if (!sequelize.Sequelize.Op) throw new Error("Sequelize operator not found."); Object.getOwnPropertySymbols(root).forEach(rootSymbol => { if (mappedValueOperators.includes(rootSymbol)) { const tmp = root[rootSymbol]; delete root[rootSymbol]; const targetObj = tmp[0]; const key = Object.keys(targetObj)[0]; const value = targetObj[key]; if (!!value.constructor && value.constructor.name === "Where") { [root] = tmp; } else { root[key] = {}; root[key][rootSymbol] = value; } } else { root[rootSymbol].forEach((obj, index) => { root[rootSymbol][index] = transformTree(obj, sequelize); }); } }); return root; } function parseFunction(obj, root, baseOperator, sequelize) { if (obj.type !== "functioncall") throw new Error("Type is not a functioncall."); if (!Object.prototype.hasOwnProperty.call(obj, "func")) throw new Error("Function not found."); if (!Object.prototype.hasOwnProperty.call(obj, "args")) throw new Error("Args not found."); if (!sequelize.where) throw new Error("Sequelize where function not found."); const dbFunction = [ "tolower", "toupper", "trim", "year", "month", "day", "hour", "minute", "second" ]; const { args } = obj; const tmp = {}; let value = ""; const operator = obj.func === "substringof" ? getOperator(obj.func, sequelize) : baseOperator || getOperator(obj.func, sequelize); let key = ""; const keyFilter = args.filter(t => { return Object.prototype.hasOwnProperty.call(t, "name"); })[0]; let innerFuncName = ""; if (keyFilter) key = keyFilter.name; else { const innerFunc = args.filter(t => t.type === "functioncall"); if (innerFunc.length === 0) throw new Error("Unable to parse filter for args"); key = innerFunc[0].args[0].name; innerFuncName = innerFunc[0].func; } const setValue = functionName => { if (root instanceof Array) { tmp[key] = sequelize.where(sequelize.fn(functionName, sequelize.col(key)), operator, value); } else { root[key] = sequelize.where(sequelize.fn(functionName, sequelize.col(key)), operator, value); } }; if (root instanceof Array && !dbFunction.includes(obj.func)) { const tmpObj = {}; tmpObj[key] = {}; root.push(tmpObj); } else if (root instanceof Array) { root.push({}); } else { root[key] = {}; } switch (obj.func) { case "substringof": value = `%${args[0].value}%`; if (innerFuncName) setValue(innerFuncName); break; case "startswith": value = `${args[0].value}%`; break; case "endswith": value = `%${args[0].value}`; break; case "tolower": case "toupper": case "trim": case "year": case "month": case "day": case "hour": case "minute": case "second": setValue(obj.func); break; default: break; } if (root instanceof Array) { if (!dbFunction.includes(obj.func)) { // tmp[key] = value; root[root.length - 1][key][operator] = value; } else { root[root.length - 1] = tmp; } } else if (!dbFunction.includes(obj.func) && !innerFuncName) { root[key][operator] = value; } } function parseFunctionCall(obj, root, operator, sequelize) { if (obj.type !== "functioncall") throw new Error("Type is not a functioncall."); switch (obj.func) { case "substringof": case "startswith": case "endswith": case "tolower": case "toupper": case "trim": case "year": case "month": case "day": case "hour": case "minute": case "second": parseFunction(obj, root, operator, sequelize); break; default: break; } } function preOrderTraversal(root, baseObj, operator, sequelize) { const strOperator = root.type === "functioncall" ? root.func : root.type; if (root.type !== "property" && root.type !== "literal" && root.type !== "functioncall") operator = strOperator ? getOperator(strOperator, sequelize) : operator; if (root.type === "functioncall") { parseFunctionCall(root, baseObj, operator, sequelize); } else if (root.type === "property" || root.type === "literal") { if (root.type === "property") { baseObj.push({}); const { length } = baseObj; baseObj[length - 1][root.name] = ""; } else { const { length } = baseObj; const key = Object.keys(baseObj[length - 1])[0]; if (Object.prototype.hasOwnProperty.call(baseObj[length - 1][key], "logic")) { baseObj[length - 1][key].logic = root.value; } else { baseObj[length - 1][key] = root.value; } } } else if (baseObj instanceof Array) { baseObj.push({}); const { length } = baseObj; baseObj[length - 1][operator] = []; } else { baseObj[operator] = []; } if (root.left) { if (baseObj instanceof Array && baseObj.length > 0) { const { length } = baseObj; preOrderTraversal(root.left, baseObj[length - 1][operator], operator, sequelize); } else { preOrderTraversal(root.left, baseObj[operator], operator, sequelize); } } if (root.right) { if (baseObj instanceof Array && baseObj.length > 0) { const { length } = baseObj; preOrderTraversal(root.right, baseObj[length - 1][operator], operator, sequelize); } else { preOrderTraversal(root.right, baseObj[operator], operator, sequelize); } } return baseObj; } /** * Parse OData expression * @param {string} expression * @return {object} parsed object */ function parseExpression(expression) { return parser.parse(expression); } /** * Parse $select * @param {string} $select * @return {object} parsed object */ function parseSelect($select) { const attributes = []; if ($select) { $select.forEach(element => { attributes.push(element); }); } return attributes.length > 0 ? { attributes } : {}; } /** * Parse $top * @param {string} $top * @return {number} number of registers to fetch */ function parseTop($top) { let limit = 0; if ($top) { limit = $top; } return limit > 0 ? { limit } : {}; } /** * Parse $skip * @param {string} $skip * @return {number} number of registers to skip */ function parseSkip($skip) { let offset = 0; if ($skip) { offset = $skip; } return offset > 0 ? { offset } : {}; } /** * Parse $filter * @param {string} $filter * @return {object} parsed object */ function parseFilter($filter, sequelize) { if (!sequelize.Sequelize.Op) throw new Error("Sequelize operator not found."); if (!$filter) { return {}; } const tree = preOrderTraversal($filter, {}, null, sequelize); const parsed = transformTree(tree, sequelize); return parsed ? { where: parsed } : {}; } /** * Parse $orderby * @param {string} $orderby * @return {object} parsed object */ function parseOrderBy($orderby) { const order = []; if ($orderby) { $orderby.forEach(element => { const arr = []; const key = Object.keys(element)[0]; arr.push(key); arr.push((element[key] || "asc").toUpperCase()); order.push(arr); }); } return order.length > 0 ? { order } : {}; } /** * Parse an OData string to a sequelize query object * @param {string2Parse} OData string to parse * @param {sequelize} Sequelize instance * @return {object} filled object to pass as query argument */ module.exports = (string2Parse, sequelize) => { if (!sequelize) throw new Error("Sequelize instance is required."); if (!sequelize.Sequelize.Op) throw new Error("Sequelize operator not found."); mapOperators(sequelize); const expression = parseExpression(string2Parse); const attributes = parseSelect(expression.$select); const top = parseTop(expression.$top); const skip = parseSkip(expression.$skip); const orderby = parseOrderBy(expression.$orderby); const filter = parseFilter(expression.$filter, sequelize); return Object.assign({}, attributes, top, skip, orderby, filter); };