UNPKG

@sap/xsodata

Version:

Expose data from a HANA database as OData V2 service with help of .xsodata files.

1,228 lines (1,097 loc) 33.2 kB
'use strict'; //Includes const NotSupported = require('./errors/http/notSupported'); const uriToDb = require('./../utils/typeConverters/uriToDb').uriToDb; const XsODataTypeError = require('./errors/typeError'); //Supported expression kinds exports.NODE_LITERAL = 1; //TODO exports.NODE_LITERAL_NUMBER = 2; exports.NODE_LITERAL_DATETIME = 3; exports.NODE_LITERAL_TIME = 4; exports.NODE_LITERAL_BOOLEAN = 5; exports.NODE_LITERAL_GUID = 6; exports.NODE_LITERAL_BINARY = 7; exports.NODE_LITERAL_STRING = 8; exports.NODE_LITERAL_NULL = 9; exports.NODE_LITERAL_NIL = 10; exports.NODE_LITERAL_MAX_VALUE = 19; exports.NODE_BINARY = 20; exports.NODE_NEGATE = 21; exports.NODE_UNARY = 22; exports.NODE_METHOD = 23; exports.NODE_PARENTHESIS = 24; exports.NODE_MEMBER = 25; exports.NODE_PROPERTY = 26; exports.NODE_SORTORDER = 27; exports.NODE_ORDERBY = 28; //Supported binary operators exports.OR = 0; exports.AND = 1; exports.EQ = 2; exports.NE = 3; exports.GE = 4; exports.LE = 5; exports.GT = 6; exports.LT = 7; exports.ADD = 8; exports.SUB = 9; exports.MUL = 10; exports.DIV = 11; exports.MOD = 12; exports.binarySqlMapping = [ ' or ', ' and ', ' = ', ' != ', ' >= ', ' <= ', ' > ', ' < ', ' + ', ' - ', ' * ', ' / ', ' mod ', ]; //Supported unary operators exports.NOT = 0; exports.NEGATE = 1; exports.unarySqlMapping = ['not ', '-']; //Supported methods exports.STARTSWITH = 'startswith'; exports.ENDSWITH = 'endswith'; exports.SUBSTRING = 'substring'; exports.SUBSTRINGOF = 'substringof'; exports.INDEXOF = 'indexof'; exports.REPLACE = 'replace'; exports.TOLOWER = 'tolower'; exports.TOUPPER = 'toupper'; exports.TRIM = 'trim'; exports.CONCAT = 'concat'; exports.LENGTH = 'length'; exports.YEAR = 'year'; exports.MONTH = 'month'; exports.DAY = 'day'; exports.HOUR = 'hour'; exports.MINUTE = 'minute'; exports.SECOND = 'second'; exports.ROUND = 'round'; exports.CEILING = 'ceiling'; exports.FLOOR = 'floor'; exports.Number = ONumber; exports.DateTime = DateTime; exports.Time = Time; exports.Binary = Binary; exports.Guid = Guid; exports.Boolean = OBoolean; exports.EdmString = EdmString; exports.BinaryOperator = BinaryOperator; exports.UnaryOperator = UnaryOperator; exports.Parenthesis = Parenthesis; exports.Method = Method; exports.Member = Member; exports.Property = Property; exports.SortOrder = SortOrder; exports.OrderBy = OrderBy; exports.Null = Null; exports.Nil = Nil; exports.DbValue = DbValue; exports.cloneContextWithNoAlias = cloneContextWithNoAlias; //Code function DbValue(value, dbType) { this.value = value; this.dbType = dbType; } DbValue.prototype.toSqlHana = function toSqlHana(context, parameter) { if (this.value.toSqlHana) { if (this.dbType) { return this.value.toSqlHana(context, parameter, { useDbType: this.dbType, }); } else { return this.value.toSqlHana(context, parameter); } } else { parameter.push(this.value); return '?'; } }; DbValue.prototype.getChildren = function () { return []; }; /** Edm.Time representation * @class Time * Tested via test_filter_parser.js */ function Time(value) { this.kind = exports.NODE_LITERAL_TIME; this.value = value; } /** Conversion to HANA db type Secondtime * Sample from HANA xsengine logger file: * Secondtime, value: 11:12:13 */ Time.prototype.toSqlHana = function toSqlHana(context, parameter) { let value = uriToDb['Edm.Time'](this.value, 'TIME'); parameter.push(value); return '?'; }; Time.prototype.getChildren = function () { return []; }; /** Edm.Time representation * @class Time * Tested via test_filter_parser.js */ function Nil() { this.kind = exports.NODE_LITERAL_NIL; } /** Conversion to HANA db type Secondtime * Sample from HANA xsengine logger file: * Secondtime, value: 11:12:13 */ Nil.prototype.toSqlHana = function toSqlHana() { throw new XsODataTypeError('Nil can not converted to HANA sql syntax'); }; Nil.prototype.getChildren = function () { return []; }; /** Edm.Time representation * @class Time * Tested via test_filter_parser.js */ function Null() { this.kind = exports.NODE_LITERAL_NULL; } /** Conversion to HANA db type Secondtime * Sample from HANA xsengine logger file: * Secondtime, value: 11:12:13 */ Null.prototype.toSqlHana = function toSqlHana() { return 'null'; }; Null.prototype.getChildren = function () { return []; }; /** Edm.Time representation * @class DateTime * Tested via test_filter_parser.js */ function DateTime(value) { this.kind = exports.NODE_LITERAL_DATETIME; this.value = value; } /** Conversion to HANA db type Secondtime * Sample from HANA xsengine logger file: * Longdate, value: 1999-01-02 03:45:00.0000000 */ DateTime.prototype.toSqlHana = function (context, parameter, hint) { if (hint && hint.useDbType) { let value = uriToDb['Edm.DateTime'](this.value, hint.useDbType); parameter.push(value); } else { let value = uriToDb['Edm.DateTime'](this.value, 'TIMESTAMP'); parameter.push(value); } return '?'; }; DateTime.prototype.getChildren = function () { return []; }; /** Edm.Byte, Edm.Int16, Edm.Int32, Edm.Int64, Edm.Decimal, Edm.Single, Edm.Double representation * @class ONumber * @param {string} number e.g. >1.552< * Tested via test_filter_parser.js */ function ONumber(number /*intPart, floatPart, exp, postFix*/) { this.kind = exports.NODE_LITERAL_NUMBER; this.uriNumber = number; this.preNumber = undefined; let type = number.slice(number.length - 1).toUpperCase(); if ( type === 'F' || type === 'f' || type === 'D' || type === 'd' || type === 'M' || type === 'm' || type === 'L' || type === 'l' ) { number = number.substring(0, number.length - 1); this.type = type; } else { this.type = null; } this.sign = 1; // > 0 let exponent; if (number.indexOf('E') !== -1) { exponent = number.split('E'); } else if (number.indexOf('e') !== -1) { exponent = number.split('e'); } else { exponent = [number]; } let intFloat = exponent[0].split('.'); this.i = intFloat[0] || null; this.f = intFloat[1] || null; this.e = exponent[1] || null; this.preNumber = ''; if (this.i) { this.preNumber += this.i; } else { this.preNumber += 0; } if (this.f) { this.preNumber += '.' + this.f; } if (this.e) { this.preNumber += 'e' + this.e; } } /** Conversion to HANA db type Secondtime * Sample fro * HANA xsengine logger file: * Integer, value: 1 * BigInt, value: 4294836224 * BigInt, value: -4294836224 */ ONumber.prototype.toSqlHana = function toSqlHana(context, parameter) { let input; if (this.sign === 1) { input = this.preNumber; } else { input = '-' + this.preNumber; } let dbValue; if (this.type === null) { //simple TINYINT(Edm.Byte),SMALLINT(edm.Int16),INTEGER(Edm.Int32) all over Edm.Int32 dbValue = parseInt(input); } else if (this.type === 'F') { dbValue = parseFloat(input); } else if (this.type === 'D') { dbValue = parseFloat(input); } else if (this.type === 'M') { dbValue = input; } else if (this.type === 'L') { dbValue = input; } else { throw new XsODataTypeError(this.type); } parameter.push(dbValue); return '?'; }; ONumber.prototype.getChildren = function () { return []; }; /** Edm.String representation * @class EdmString * Tested via test_filter_parser.js */ function EdmString(value) { this.kind = exports.NODE_LITERAL_STRING; this.string = value; } /** Conversion to HANA db type NString * Sample from HANA xsengine logger file: * NString, value: 'st r''ing' */ EdmString.prototype.toSqlHana = function toSqlHana(context, parameter) { let value = uriToDb['Edm.String'](this.string, 'VARCHAR'); parameter.push(value); return '?'; }; EdmString.prototype.getChildren = function () { return []; }; /** Edm.Binary representation * @class Binary * Tested via test_filter_parser.js */ function Binary(value) { this.kind = exports.NODE_LITERAL_BINARY; this.value = value; } /** Conversion to HANA db type BString * Sample from HANA xsengine logger file: * BString, value: x'dcba00' */ Binary.prototype.toSqlHana = function toSqlHana(context, parameter) { let value = uriToDb['Edm.Binary'](this.value, 'VARBINARY'); parameter.push(value); return '?'; }; Binary.prototype.getChildren = function () { return []; }; /** Edm.Binary representation * @class Guid * Tested via test_filter_parser.js */ function Guid(value) { this.kind = exports.NODE_LITERAL_GUID; this.guid = value; } Guid.prototype.toSqlHana = function toSqlHana(/*context*/) { throw new NotSupported('GUID ODataType not supporeted'); }; Guid.prototype.getChildren = function () { return []; }; function OBoolean(value) { this.kind = exports.NODE_LITERAL_BOOLEAN; this.value = value; } OBoolean.prototype.toSqlHana = function toSqlHana(/*context, parameter*/) { //see ResourceHandling/OData/SqlProcessor.cpp //Method sqlProcessor::serializeExpr case Expr::LITERAL //But be aware the $filter= true eq true //becomes WHERE (1=1)=(1=1) //and will lead to an SQL syntax error if (this.value) { return '(1=1)'; } else { return '(1=0)'; } }; OBoolean.prototype.getChildren = function () { return []; }; /** BinaryOperator * @class BinaryOperator * Tested via test_filter_parser.js * @param {Number} op (e.g. exports.AND) * @param left * @param right * @param strict Use direct "left=right" in sql generation, not the lax OData version like "(left=right) or (( left is null) and (right is null))" * @constructor */ function BinaryOperator(op, left, right, strict) { this.kind = exports.NODE_BINARY; this.op = op; this.left = left; this.right = right; this.strict = strict; // use strict = false if null is supported } BinaryOperator.prototype.setAlias = function setAlias(alias) { if (this.left.setAlias) { this.left.setAlias(alias); } if (this.right.setAlias) { this.right.setAlias(alias); } return '?'; }; BinaryOperator.prototype.applyConverter = createApplyConverter('left', 'right'); BinaryOperator.prototype.getChildren = function () { return [this.left, this.right]; }; function isLiteral(node) { return 0 < node.kind && node.kind < exports.NODE_LITERAL_MAX_VALUE; } function isNull(node) { return node.kind === exports.NODE_LITERAL_NULL; } //see ResourceHandling/OData/SqlProcessor.cpp function complexBinaryWithNull(first, op, second, context, parameter) { const norm = classifyAndNormalize(first, second); const pLeft = [], pRight = []; const left = norm.first.toSqlHana(context, pLeft); const right = norm.second.toSqlHana(context, pRight); switch (norm.literal) { case 0: return handleZeroLiteralCase( first, second, op, left, right, pLeft, pRight, context, parameter ); case 1: return handleOneLiteralCase( norm, op, left, right, pLeft, pRight, context, parameter ); case 2: return handleTwoLiteralCase( norm.isNull, op, left, right, pLeft, pRight, parameter ); default: return ''; } } function classifyAndNormalize(first, second) { let literal = 0; let _null = false; if (isLiteral(second)) { literal++; if (isNull(second)) { _null = true; } } if (isLiteral(first)) { literal++; if (!_null) { if (isNull(first)) { _null = true; const tmp = second; second = first; first = tmp; } else if (literal === 1) { const tmp = second; second = first; first = tmp; } } } return { first: first, second: second, literal: literal, isNull: _null }; } function handleZeroLiteralCase( first, second, op, left, right, pLeft, pRight, context, parameter ) { if (context.isNav(first.property) && context.isNav(second.property)) { if (op === exports.EQ) { throw new Error( "Operator 'eq' incompatible with operand types '<Collection>' and '<Collection>'." ); } throw new Error( "Operator 'ne' incompatible with operand types '<Collection>' and '<Collection>'." ); } if (op === exports.EQ) { const sql = '(' + left + ' = ' + right + ' or (' + left + ' is null and ' + right + ' is null))'; pushParams(parameter, pLeft, pRight, pLeft, pRight); return sql; } const sql = '((' + left + ' != ' + right + ' and not (' + left + ' is null and ' + right + ' is null))' + ' or (' + left + ' is null and ' + right + ' is not null)' + ' or (' + left + ' is not null and ' + right + ' is null))'; pushParams( parameter, pLeft, pRight, pLeft, pRight, pLeft, pRight, pLeft, pRight ); return sql; } function handleOneLiteralCase( norm, op, left, right, pLeft, pRight, context, parameter ) { const first = norm.first; const _null = norm.isNull; if (context.isNav(first.property)) { if (_null) { throw new NotSupported( "Comparing operand type '<Collection>' with null not supported.", 406 ); } if (op === exports.EQ) { throw new Error( "Operator 'eq' incompatible with operand types '<Collection>' and non '<Collection>'." ); } throw new Error( "Operator 'ne' incompatible with operand types '<Collection>' and non '<Collection>'." ); } if (op === exports.EQ) { const comparator = _null ? ' is ' : ' = '; const sql = '(' + left + comparator + right + ')'; pushParams(parameter, pLeft, pRight); return sql; } if (_null) { const sql = '(' + left + ' is not ' + right + ')'; pushParams(parameter, pLeft, pRight); return sql; } const sql = '(' + left + ' != ' + right + ' or ' + left + ' is null)'; pushParams(parameter, pLeft, pRight, pLeft); return sql; } function handleTwoLiteralCase( isNullLiteral, op, left, right, pLeft, pRight, parameter ) { if (op === exports.EQ) { const comparator = isNullLiteral ? ' is ' : ' = '; const sql = '(' + left + comparator + right + ')'; pushParams(parameter, pLeft, pRight); return sql; } const comparator = isNullLiteral ? ' is not ' : ' != '; const sql = '(' + left + comparator + right + ')'; pushParams(parameter, pLeft, pRight); return sql; } function pushParams(parameter) { for (let i = 1; i < arguments.length; i++) { const arr = arguments[i]; parameter.push.apply(parameter, arr); } } BinaryOperator.prototype.getTypeHint = function () { if (this.left.kind === exports.NODE_PROPERTY) { return { applyOn: 'right', dbType: this.left.dbType, }; } else if (this.right.kind === exports.NODE_PROPERTY) { return { applyOn: 'left', dbType: this.right.dbType, }; } return null; }; BinaryOperator.prototype.toSqlHana = function toSqlHana(context, parameter) { this.kind = exports.NODE_BINARY; let pLeft = []; let pRight = []; let castType = this.getTypeHint(); let left = null; let right = null; if (castType) { if (castType.applyOn === 'left') { left = this.left.toSqlHana(context, pLeft, { useDbType: castType.dbType, }); right = this.right.toSqlHana(context, pRight); } if (castType.applyOn === 'right') { left = this.left.toSqlHana(context, pLeft); right = this.right.toSqlHana(context, pRight, { useDbType: castType.dbType, }); } } else { left = this.left.toSqlHana(context, pLeft); right = this.right.toSqlHana(context, pRight); } let sql = ''; switch (this.op) { case exports.EQ: case exports.NE: //special case if (this.strict || context.oDataNullSupport !== true) { sql += '(' + left + exports.binarySqlMapping[this.op] + right + ')'; parameter.push.apply(parameter, pLeft); parameter.push.apply(parameter, pRight); break; } sql += complexBinaryWithNull( this.left, this.op, this.right, context, parameter ); break; case exports.OR: case exports.AND: case exports.GE: case exports.LE: case exports.GT: case exports.LT: case exports.ADD: case exports.SUB: case exports.MUL: case exports.DIV: sql += '(' + left + exports.binarySqlMapping[this.op] + right + ')'; parameter.push.apply(parameter, pLeft); parameter.push.apply(parameter, pRight); break; case exports.MOD: sql += '(mod(' + left + ',' + right + '))'; parameter.push.apply(parameter, pLeft); parameter.push.apply(parameter, pRight); break; } return sql; }; /** UnaryOperator * @class UnaryOperator * Tested via test_filter_parser.js */ function UnaryOperator(op, value) { this.kind = exports.NODE_UNARY; this.op = op; this.value = value; } UnaryOperator.prototype.toSqlHana = function toSqlHana(context, parameter) { let p = []; let value = this.value.toSqlHana(context, p); parameter.push.apply(parameter, p); return '(' + exports.unarySqlMapping[this.op] + value + ')'; }; UnaryOperator.prototype.setAlias = function (alias) { if (this.value.setAlias) { this.value.setAlias(alias); } }; UnaryOperator.prototype.applyConverter = createApplyConverter('value'); UnaryOperator.prototype.getChildren = function () { return [this.value]; }; /** Method * @class UnaryOperator * Tested via test_filter_parser.js */ function Method(method, parameter) { this.kind = exports.NODE_METHOD; this.method = method; this.parameter = parameter; } Method.prototype.setAlias = function (alias) { for (const param of this.parameter) { if (param.setAlias) { param.setAlias(alias); } } }; Method.prototype.applyConverter = createApplyConverter('parameter'); Method.prototype.getChildren = function () { return this.parameter; }; Method.prototype.toSqlHana = function toSqlHana(context, parameter) { let sql = ''; let p0 = []; let p1 = []; let p2 = []; // SAP HANA SQL and System Views Reference // LIKE Predicate at: https://help.sap.com/viewer/4fe29514fd584807ac9f2a04f6754767/2.0.02/en-US/20fa17f375191014a4d8d8cbfddfe340.html let inP0; let inP1; switch (this.method) { case exports.STARTSWITH: //start sql = '(' + this.parameter[0].toSqlHana(context, p0); sql += ' LIKE '; inP1 = this.parameter[1]; if (inP1.kind === exports.NODE_LITERAL_STRING) { if (!inP1.string.includes('_') && !inP1.string.includes('%')) { // no escaping of the pattern required sql += 'concat(' + inP1.toSqlHana(context, p1) + ",'%')"; sql += ')'; //end parameter.push.apply(parameter, p0); parameter.push.apply(parameter, p1); break; } } sql += 'concat(replace(replace(replace('; sql += inP1.toSqlHana(context, p1); sql += ",'\\','\\\\'),'%','\\%'),'_','\\_'),'%')"; sql += " escape '\\'"; sql += ')'; //end parameter.push.apply(parameter, p0); parameter.push.apply(parameter, p1); break; case exports.ENDSWITH: sql = '(' + this.parameter[0].toSqlHana(context, p0); sql += ' LIKE '; inP1 = this.parameter[1]; if (inP1.kind === exports.NODE_LITERAL_STRING) { if (!inP1.string.includes('_') && !inP1.string.includes('%')) { // no escaping of the pattern required sql += "concat('%'," + inP1.toSqlHana(context, p1) + ')'; sql += ')'; //end parameter.push.apply(parameter, p0); parameter.push.apply(parameter, p1); break; } } sql += "concat('%', replace(replace(replace("; sql += inP1.toSqlHana(context, p1); sql += ",'\\','\\\\'),'%','\\%'),'_','\\_')"; sql += ')'; sql += " escape '\\'"; sql += ')'; //end parameter.push.apply(parameter, p0); parameter.push.apply(parameter, p1); break; case exports.SUBSTRING: if (this.parameter[2]) { sql = '' + 'SUBSTRING(' + this.parameter[0].toSqlHana(context, p0); sql += ',' + this.parameter[1].toSqlHana(context, p1) + '+1'; sql += ',' + this.parameter[2].toSqlHana(context, p2) + ')'; sql += ''; parameter.push.apply(parameter, p0); parameter.push.apply(parameter, p1); parameter.push.apply(parameter, p2); } else { sql = '' + 'SUBSTRING(' + this.parameter[0].toSqlHana(context, p0); sql += ',' + this.parameter[1].toSqlHana(context, p1) + '+1' + ')'; sql += ''; parameter.push.apply(parameter, p0); parameter.push.apply(parameter, p1); } break; case exports.SUBSTRINGOF: //true if the first parameter is SUBSTRINGOf the second parameter sql = '(' + this.parameter[1].toSqlHana(context, p0); sql += ' LIKE '; inP0 = this.parameter[0]; if (inP0.kind === exports.NODE_LITERAL_STRING) { if (!inP0.string.includes('_') && !inP0.string.includes('%')) { // no escaping of the pattern required sql += "concat('%'," + 'concat(' + inP0.toSqlHana(context, p1) + ",'%')" + ')'; sql += ')'; parameter.push.apply(parameter, p0); parameter.push.apply(parameter, p1); break; } } sql += "concat('%',concat(replace(replace(replace("; sql += inP0.toSqlHana(context, p1); sql += ",'\\','\\\\'),'%','\\%'),'_','\\_')"; sql += ",'%'))"; sql += " escape '\\'"; sql += ')'; parameter.push.apply(parameter, p0); parameter.push.apply(parameter, p1); break; case exports.INDEXOF: sql = '(-1)+LOCATE('; sql += this.parameter[0].toSqlHana(context, p0) + ','; sql += this.parameter[1].toSqlHana(context, p1) + ')'; parameter.push.apply(parameter, p0); parameter.push.apply(parameter, p1); break; case exports.REPLACE: sql = 'REPLACE('; sql += this.parameter[0].toSqlHana(context, p0) + ','; sql += this.parameter[1].toSqlHana(context, p1) + ','; sql += this.parameter[2].toSqlHana(context, p2) + ')'; parameter.push.apply(parameter, p0); parameter.push.apply(parameter, p1); parameter.push.apply(parameter, p2); break; case exports.TOLOWER: sql = 'LCASE('; sql += this.parameter[0].toSqlHana(context, p0) + ')'; parameter.push.apply(parameter, p0); break; case exports.TOUPPER: sql = 'UCASE('; sql += this.parameter[0].toSqlHana(context, p0) + ')'; parameter.push.apply(parameter, p0); break; case exports.TRIM: sql = 'TRIM('; sql += this.parameter[0].toSqlHana(context, p0) + ')'; parameter.push.apply(parameter, p0); break; case exports.CONCAT: sql = 'CONCAT('; sql += this.parameter[0].toSqlHana(context, p0) + ','; sql += this.parameter[1].toSqlHana(context, p1) + ')'; parameter.push.apply(parameter, p0); parameter.push.apply(parameter, p1); break; case exports.LENGTH: sql = 'LENGTH('; sql += this.parameter[0].toSqlHana(context, p0) + ')'; parameter.push.apply(parameter, p0); break; case exports.YEAR: sql = 'YEAR('; sql += this.parameter[0].toSqlHana(context, p0) + ')'; parameter.push.apply(parameter, p0); break; case exports.MONTH: sql = 'MONTH('; sql += this.parameter[0].toSqlHana(context, p0) + ')'; parameter.push.apply(parameter, p0); break; case exports.DAY: sql = 'DAYOFMONTH('; sql += this.parameter[0].toSqlHana(context, p0) + ')'; parameter.push.apply(parameter, p0); break; case exports.HOUR: sql = 'HOUR('; sql += this.parameter[0].toSqlHana(context, p0) + ')'; parameter.push.apply(parameter, p0); break; case exports.MINUTE: sql = 'MINUTE('; sql += this.parameter[0].toSqlHana(context, p0) + ')'; parameter.push.apply(parameter, p0); break; case exports.SECOND: sql = 'SECOND('; sql += this.parameter[0].toSqlHana(context, p0) + ')'; parameter.push.apply(parameter, p0); break; case exports.ROUND: sql = 'ROUND('; sql += this.parameter[0].toSqlHana(context, p0) + ')'; parameter.push.apply(parameter, p0); break; case exports.CEILING: sql = 'CEIL('; sql += this.parameter[0].toSqlHana(context, p0) + ')'; parameter.push.apply(parameter, p0); break; case exports.FLOOR: sql = 'FLOOR('; sql += this.parameter[0].toSqlHana(context, p0) + ')'; parameter.push.apply(parameter, p0); break; } return sql; }; /** Parenthesis * @class Parenthesis * Tested via test_filter_parser.js */ function Parenthesis(value) { this.kind = exports.NODE_PARENTHESIS; this.value = value; } Parenthesis.prototype.toSqlHana = function toSqlHana(context, parameter) { let p = []; let ret = '(' + this.value.toSqlHana(context, p) + ')'; parameter.push.apply(parameter, p); return ret; }; Parenthesis.prototype.setAlias = function (alias) { if (this.value.setAlias) { this.value.setAlias(alias); } }; Parenthesis.prototype.applyConverter = createApplyConverter('value'); Parenthesis.prototype.getChildren = function () { return [this.value]; }; /** Member * @class Member * Tested via test_filter_parser.js */ function Member(path, property) { this.kind = exports.NODE_MEMBER; this.path = path; this.property = property; } Member.prototype.toSqlHana = function toSqlHana() { throw new NotSupported('Member access not supported'); }; Member.prototype.getChildren = function () { return []; }; /** Property * @class Property * Tested via test_filter_parser.js */ function Property(property, table, dbType) { this.kind = exports.NODE_PROPERTY; this.property = property; this.table = table; this.dbType = dbType; } Property.prototype.toSqlHana = function toSqlHana(context) { let ret = ''; let table = this.table ? this.table : context.table; if (table) { ret += '"' + table + '"' + '.'; } ret += '"' + this.property + '"'; return ret; }; Property.prototype.setAlias = function (alias) { this.table = alias; }; Property.prototype.getChildren = function () { return []; }; /** SortOrder * @class SortOrder * Tested via test_oderby_parser.js */ function SortOrder(expression, ascending) { this.kind = exports.NODE_SORTORDER; this.expression = expression; this.ascending = ascending; } SortOrder.prototype.setPropertyName = function (propertyName) { this.propertyName = propertyName; }; SortOrder.prototype.getPropertyName = function () { return this.propertyName; }; SortOrder.prototype.setAlias = function (alias) { this.expression.setAlias(alias); }; SortOrder.prototype.toSqlHana = function toSqlHana(context, parameter) { let sql = ''; let p = []; sql += this.expression.toSqlHana(context, p); parameter.push.apply(parameter, p); if (this.ascending === false) { sql += ' DESC'; } sql += ''; return sql; }; SortOrder.prototype.applyConverter = createApplyConverter('expression'); SortOrder.prototype.getChildren = function () { return [this.expression]; }; /** SortOrder * @class SortOrder * Tested via test_oderby_parser.js */ function OrderBy() { this.kind = exports.NODE_ORDERBY; this.orders = []; } OrderBy.prototype.addSortOrder = function (sortOrders) { this.orders.push(sortOrders); }; OrderBy.prototype.applyConverter = function (convert) { this.orders.forEach(function (child) { child.applyConverter(convert); }); }; OrderBy.prototype.setAlias = function (alias) { for (const order of this.orders) { order.setAlias(alias); } }; OrderBy.prototype.toSqlHana = function toSqlHana(context, parameter) { let sql = ''; let clonedContext = cloneContextWithNoAlias(context); clonedContext.inOrderBy = true; for (let i = 0; i < this.orders.length; i++) { sql += i === 0 ? '' : ', '; sql += this.orders[i].toSqlHana(clonedContext, parameter); } return sql; }; OrderBy.prototype.getChildren = function () { return this.orders; }; /** * Creates an applyConverter function for the given property names. * * @returns {Function} applyConvert to be attached to an object, who properties shall be converted */ function createApplyConverter() { let childPropertyNames = [].slice.call(arguments); /** * The created function takes a converter function as argument, which * takes a property to be converted. The value returned by this function * will then replace the property on the object in which context applyConverter * has been called. * * Afterwards, if the property also has an applyConverter, it is called with the * same converter function. * @param function to be called with the configured properties */ return function applyConverter(convert) { childPropertyNames.forEach( function (property) { this[property] = convert(this[property]); if (this[property].applyConverter) { this[property].applyConverter(convert); } }.bind(this) ); }; } function cloneContextWithNoAlias(context) { let clonedContext; if (context.clone) { clonedContext = context.clone(); } else { clonedContext = {}; } clonedContext.noAlias = true; return clonedContext; }