@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
JavaScript
'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;
}