@sap/xsodata
Version:
Expose data from a HANA database as OData V2 service with help of .xsodata files.
1,690 lines (1,415 loc) • 46.4 kB
JavaScript
;
var edm = require('./../utils/typedObjects');
var cloneContextWithNoAlias = edm.cloneContextWithNoAlias;
var InternalError = require('./../utils/errors/internalError');
var util = require('util');
/**
* Create a new SqlBuildHanaContext
* The buildContext is used as parameter for the toSqlHana functions
* The buildContext stores whether null values are supported or not and
* @param context
* @constructor
*/
exports.SqlBuildHanaContext = function (context) {
if (context) {
this.oDataNullSupport = context.gModel.isNullSupported();
var dbSeg = context.oData.dbSegmentLast;
this.isNav = function (string) {
return dbSeg._SelectedNavigations.indexOf(string) > -1;
};
this.isHanaCloudDb = getHanaCloudDbSetting(context);
} else {
this.oDataNullSupport = false;
this.isNav = false;
this.isHanaCloudDb = true; // use default Hana Cloud - Q: Is this needed? In a clone (without context, the original context should have this setting)
}
this.inOrderBy = false;
this.table = null;
this.noAlias = null; // only used in a cloned SqlBuildHanaContext to avoid using aliases in orderby clauses
};
function getHanaCloudDbSetting(context) {
// 06/2022: All used SQL from Hana Cloud DB work also on Hana Service DB,
// i.e. take that as default; but that may change in the future!
let isHanaCloudDb = true;
if (context.batchContext && context.batchContext.parentContext && context.batchContext.parentContext.db && context.batchContext.parentContext.db.isHanaCloudDb !== undefined) {
isHanaCloudDb = context.batchContext.parentContext.db.isHanaCloudDb; // batch request context
} else if (context.db && context.db.isHanaCloudDb !== undefined) {
isHanaCloudDb = context.db.isHanaCloudDb; // regular request context
}
return isHanaCloudDb;
}
exports.SqlBuildHanaContext.prototype.clone = function () {
var c = new exports.SqlBuildHanaContext();
c.oDataNullSupport = this.oDataNullSupport;
c.isNav = this.isNav;
c.table = this.table; // used in Property.prototype.toSqlHana
c.noAlias = this.noAlias;
c.inOrderBy = this.inOrderBy;
c.isHanaCloudDb = this.isHanaCloudDb; // keep this setting in any clone!!!
return c;
};
const extractStms = (stmDbset) => {
if (!stmDbset) {
return [];
}
return stmDbset.map(x => x.stm);
};
exports.PostContainer = class PostContainer {
constructor() {
this.createTmp = null;
this.insertTmp = null;
this.insertReal = null;
this.select = null;
}
};
exports.GetContainer = class GetContainer {
constructor() {
this.createTmp = null;
this.insertTmp = null;
this.selectTmp = null;
this.select = null; //if there is no $expand or $count is set or $value is set
}
};
exports.GetContainerAll = class GetContainerAll {
constructor() {
this.createTmp = [];
this.insertTmp = [];
this.selectTmp = [];
//if there is no $expand or $count is set or $value is set
this.select = [];
}
getCreateTmpStms() {
return extractStms(this.createTmp);
}
getInsertTmpStms() {
return extractStms(this.insertTmp);
}
getSelectTmpStms() {
return extractStms(this.selectTmp);
}
getSelectStms() {
return extractStms(this.select);
}
};
exports.PutContainer = class PutContainer {
constructor() {
this.createTmpOld = null;
this.createTmp = null;
this.insertTmpOld = null;
this.insertTmp = null;
this.updateReal = null;
}
};
exports.DeleteContainer = class DeleteContainer {
constructor() {
this.createTmpDel = null;
this.insertTmpDel = null;
this.delete = null;
}
};
/**
* @Class Eq
* @constructor
* @param left
* @param right
*/
/*
function Eq(left, right) {
this.left = left;
this.right = right;
}
exports.Eq = Eq;
Eq.prototype.toSqlHana = function (context,parameter) {
var ret = '';
ret += '(' + this.left.toSqlHana(context,parameter) + ' = ' + this.right.toSqlHana(context,parameter) + ')';
return ret;
};*/
/**
* @Class Value
* @constructor
* @param value
*/
function Value(value) {
this.value = value;
}
exports.Value = Value;
Value.prototype.toSqlHana = function (context, parameter) {
parameter.push(this.value);
return '?';
};
/**
* @class Exists
* @constructor
* @param select
*/
function Exists(select) {
this.exists = select;
}
exports.Exists = Exists;
Exists.prototype.toSqlHana = function (context, parameter) {
var ret = '';
ret += 'exists (' + this.exists.toSqlHana(context, parameter) + ')';
return ret;
};
/**
* @class Property
* @constructor
* @param table
* @param property
* @param propertyType
* @param alias
*/
function SelectProperty(table, property, propertyType, alias) {
this.table = table;
this.property = property;
this.propertyType = propertyType;
this.alias = alias;
}
exports.SelectProperty = SelectProperty;
SelectProperty.prototype.toSqlHana = function (context, parameter, hint) {
var sql = [];
var conversionFunction;
if (!context.inOrderBy) {
conversionFunction = getConversionFunction(this.propertyType);
}
if (conversionFunction) {
sql.push(conversionFunction, "(");
}
if (!(hint && hint.withoutTable) && this.table) {
sql.push('"', this.table, '".');
}
sql.push('"', this.property, '"');
if (conversionFunction) {
sql.push(")");
}
if (!context.noAlias) {
if (conversionFunction || this.alias) {
sql.push(' "', (this.alias || this.property), '"');
}
}
return sql.join("");
};
/**
* Returns name of the SQL data type conversion function, which should be used in the SQL SELECT statement to retrieve
* property with the specified <code>propertyType</code>.
*
* @param {string} propertyType - type of the property, for which the data type conversion function has to be determined
* @returns {string} name of the data type conversion function or <code>undefined</code>, when no type conversion
* should be applied.
*/
function getConversionFunction(propertyType) {
if (propertyType === "DECIMAL") {
return 'to_char';
}
}
function AggregateSelectProperty(aggregation, table, property, aggregateAlias) {
this.aggregation = aggregation;
this.aggregateAlias = aggregateAlias;
this.selectProperty = new SelectProperty(table, property);
this.property = this.selectProperty.property;
this.table = this.selectProperty.table;
}
exports.AggregateSelectProperty = AggregateSelectProperty;
AggregateSelectProperty.prototype.toSqlHana = function (context, parameters) {
var sql = this.aggregation +
'(' +
this.selectProperty.toSqlHana(context, parameters) +
')';
if (this.aggregateAlias) {
sql += ' "' + this.aggregateAlias + '"';
}
return sql;
};
function ParameterSelectProperty(table, property, alias, value) {
SelectProperty.call(this, table, property, null, alias);
this.value = value;
}
ParameterSelectProperty.prototype.toSqlHana = function (context, parameter, hint) {
var _alias = this.alias || this.property;
/*
if(!_alias){
if (!withoutTable && this.table) {
_alias = this.table + '.';
}else{
_alias = '';
}
_alias = this.property;
}*/
if (this.value) {
this.value.toSqlHana(context, parameter, hint);
return util.format('? "%s"', _alias);
}
parameter.push(null);
return util.format('? "%s"', _alias);
};
exports.ParameterSelectProperty = ParameterSelectProperty;
function CreateProperty(property, type) {
this.property = property;
this.type = type;
}
exports.CreateProperty = CreateProperty;
CreateProperty.prototype.toSqlHana = function (/*context, parameter*/) {
return '"' + this.property + '" ' + this.type;
};
/**
* @class SelectFormula
* @constructor
* @param {string} table
* @param {string} formula
* @param {string} alias
*/
function SelectFormula(table, formula, alias) {
this.table = table;
this.formula = formula;
this.alias = alias;
}
exports.SelectFormula = SelectFormula;
SelectFormula.prototype.toSqlHana = function () {
var ret = '';
if (this.table) {
ret += '"' + this.table + '".';
}
ret += this.formula;
if (this.alias) {
ret += ' "' + this.alias + '"';
}
return ret;
};
/**
* @class SelectFormula
* @constructor
* @param {string=} alias
*/
function OverFormula(alias) {
this.alias = alias;
this.orders = [];
}
exports.OverFormula = OverFormula;
OverFormula.prototype.addSortOrder = function (order) {
this.orders.push(order);
};
OverFormula.prototype.addSortOrders = function (orders) {
for (var i = 0; i < orders.length; i++) {
this.orders.push(orders[i]);
}
};
OverFormula.prototype.toSqlHana = function (context, parameter) {
var clonedContext = null;
var sql = '';
sql += 'row_number() over(';
var i;
if (this.orders.length > 0) {
clonedContext = cloneContextWithNoAlias(context);
clonedContext.inOrderBy = true;
sql += '\norder by ';
for (i = 0; i < this.orders.length; i++) {
sql += (i === 0 ? '' : ', ');
sql += this.orders[i].toSqlHana(clonedContext, parameter);
}
sql += '';
}
sql += ') ';
sql += '"' + this.alias + '"';
return sql;
};
/**
* @class Create
* @constructor
*/
function Create() {
this.modifiers = [];
this.table = null;
this.as = null;
this.postModifiers = null;
this.properties = [];
this.propertieMap = {};
}
exports.Create = Create;
Create.prototype.addProperties = function (properties) {
for (let property of properties) {
if (!this.propertieMap[property.property]) {
this.properties.push(property);
this.propertieMap[property.property] = property;
}
}
};
Create.prototype.addUniqueProperties = function (properties) {
addUniqueSqlStatementsToList(properties, this.properties);
return this;
};
Create.prototype.addProperty = function (property) {
this.properties.push(property);
};
/**
* Set modifiers
* SQL: create <modifiers> table ... as
* @param {Array.<String>} modifiers
*/
Create.prototype.setModifiers = function (modifiers) {
this.modifiers = modifiers;
};
/**
* Set post modifiers
* SQL: create ... table ... as ... <postModifiers>
* @param {Array.<String>} postModifiers
*/
Create.prototype.setPostModifiers = function (postModifiers) {
this.postModifiers = postModifiers;
};
/**
* Set Table
* SQL: create ... table <table> as
* @param table
*/
Create.prototype.setTableName = function (table) {
this.table = table;
};
/**
* Set As
* SQL: create ... table ... as (<as>)
* @param as
*/
Create.prototype.setAs = function (as) {
this.as = as;
};
Create.prototype.toSqlHana = function (context, parameter) {
var i;
var sql = '';
sql += 'create ';
for (i = 0; i < this.modifiers.length; i++) {
sql += ' ' + this.modifiers[i];
}
sql += ' table ';
sql += '"' + this.table + '" ';
if (this.as) {
sql += '\nas ( ';
sql += this.as.toSqlHana(context, parameter);
sql += ') ';
}
if (this.properties.length > 0) {
sql += '(';
for (i = 0; i < this.properties.length; i++) {
sql += (i === 0 ? '' : ', ');
sql += this.properties[i].toSqlHana(context, parameter);
}
sql += ')';
}
if (this.postModifiers) {
for (i = 0; i < this.postModifiers.length; i++) {
sql += ' ' + this.postModifiers[i];
}
}
return sql;
};
/**
* @class Insert
* @constructor
*/
function Insert() {
this.intoTable = null;
this.subSelect = null;
this.nv = [];
}
exports.Insert = Insert;
/**
* Set Table
* SQL: insert into <table>
* @param {{table: String}} table
*/
Insert.prototype.setTableName = function (table) {
this.intoTable = table;
};
/**
* Set subSelect
* SQL: insert into ... select <subselect>
* @param {exports.Select} subSelect of type "Select"
*/
Insert.prototype.setSubSelect = function (subSelect) {
this.subSelect = subSelect;
};
Insert.prototype.toSqlHana = function (context, parameter) {
var sql = '';
sql += 'insert into ';
if (this.intoTable.table.substr(0, 1) === '#') {
sql += ' "' + this.intoTable.table + '" ';
if (this.intoTable.alias) {
sql += ' as "' + this.intoTable.alias + '" ';
}
} else {
if (this.intoTable.schema) {
sql += '"' + this.intoTable.schema + '".';
}
sql += '"' + this.intoTable.table + '"';
if (this.intoTable.alias) {
sql += ' as "' + this.intoTable.alias + '" ';
}
}
var i;
var tmp = '';
//sql += this.table + ' ';
//collect names
if (this.nv.length > 0) {
for (i = 0; i < this.nv.length; i++) {
tmp += (tmp.length > 0 ? ', ' : '');
tmp += this.nv[i].name.toSqlHana(context, parameter, { withoutTable: true, useDbType: this.nv[i].dbType }); // OK
}
sql += '(' + tmp + ')';
}
if (this.subSelect) {
sql += ' (';
sql += ' ' + this.subSelect.toSqlHana(context, parameter);
sql += ') ';
} else {
sql += ' values ';
tmp = '';
for (i = 0; i < this.nv.length; i++) {
tmp += (tmp.length > 0 ? ', ' : '');
if (this.nv[i].overwriteValue !== undefined) {
if (this.nv[i].overwriteValue && this.nv[i].overwriteValue.toSqlHana) {
tmp += this.nv[i].overwriteValue.toSqlHana(context, parameter, {
withoutTable: true,
useDbType: this.nv[i].dbType
});
} else {
tmp += '?';
parameter.push(this.nv[i].overwriteValue);
}
} else if (this.nv[i].formula !== undefined) {
tmp += ' ' + this.nv[i].formula;
} else if (this.nv[i].sequence) {
tmp += ' "' + this.nv[i].sequence + '".NEXTVAL ';
} else {
if (this.nv[i].value && this.nv[i].value.toSqlHana) {
tmp += this.nv[i].value.toSqlHana(context, parameter, {
withoutTable: true,
useDbType: this.nv[i].dbType
}); // Ok
} else {
//console.log('--------------'+JSON.stringify(this.nv[i].value,null,4));
tmp += '?';
parameter.push(this.nv[i].value);
}
}
}
sql += '(' + tmp + ')';
//collect values
}
return sql;
};
/**
* Add array of properties to be inserted
* @param {(exports.Property)} names
*/
Insert.prototype.addNames = function (names) {
for (var i = 0; i < names.length; i++) {
var n = names[i];
this.nv.push({
name: n,
value: undefined //null would be a valid OData value
});
}
};
Insert.prototype.setValue = function (name, value, dbType) {
for (var i = 0; i < this.nv.length; i++) {
var nv = this.nv[i];
if (nv.name.property === name) {
nv.value = value;
if (dbType) {
nv.dbType = dbType;
}
}
}
};
/**
* @class Select
* @constructor
*/
function Select() {
this.select = [];
this.from = { schema: null, table: null, alias: null };
this.froms = [];
this.joins = [];
this.whereAnded = [];
this.orders = [];
this.groupBys = [];
this.limit = null;
this.offset = null;
this.fallbackStatement = null;
this.addCalcViewHint = false;
this.customData = {}; // restriction on the count of selected records
}
exports.Select = Select;
Select.prototype.setCustomData = function (name, value) {
this.customData[name] = value;
};
Select.prototype.getCustomData = function (name) {
return this.customData[name];
};
Select.prototype.addJoin = function (table, on) {
this.joins.push({
table: table,
on: on
});
};
Select.prototype.addJoinTable = function (select, on, alias) {
this.joins.push({
tableSelect: select,
alias: alias,
on: on
});
};
Select.prototype.toSqlHana = function (context, parameter) {
var i = 0;
var clonedContext = null;
var sql = '';
sql += 'select ';
for (i = 0; i < this.select.length; i++) {
sql += (i === 0 ? '' : ', ');
sql += this.select[i].toSqlHana(context, parameter);
}
// note: $orderby properties for CV that are NOT in select list result in SQL error
// but: That was never supported by this lib!
sql += '\nfrom ';
sql += this.froms.map(function (from) {
if (from.subQuery) {
return '(' + from.subQuery.toSqlHana(context, parameter) + ')';
}
var fromString = '';
if (from.table.substr(0, 1) !== '#' && from.schema) {
fromString += '"' + from.schema + '".';
}
fromString += '"' + from.table + '"';
if (from.placeholders) {
var sql_placeholders = from.placeholders.map(function (ph) {
return ph.toSqlHana(context, parameter);
});
fromString += '(';
fromString += sql_placeholders.join(',');
fromString += ')';
}
if (from.alias) {
fromString += ' as "' + from.alias + '"';
}
return fromString;
}).join(', ');
this.joins.forEach(function (join) {
if (join.table) {
var table = join.table;
sql += ' join ';
if (table.schema) {
sql += '"' + table.schema + '".';
}
sql += '"' + table.table + '"';
if (table.alias) {
sql += ' as "' + table.alias + '"';
}
if (join.on.length > 0) {
sql += ' on ';
for (i = 0; i < join.on.length; i++) {
sql += (i === 0 ? '' : ' and ');
sql += join.on[i].toSqlHana(context, parameter);
}
}
} else if (join.tableSelect) {
sql += ' join (';
sql += join.tableSelect.toSqlHana(context, parameter);
sql += ' )';
if (join.alias) {
sql += ' "' + join.alias + '" ';
}
if (join.on.length > 0) {
sql += ' on ';
for (i = 0; i < join.on.length; i++) {
sql += (i === 0 ? '' : ' and ');
sql += join.on[i].toSqlHana(context, parameter);
}
}
}
});
if (this.whereAnded.length > 0) {
sql += '\nwhere ';
for (i = 0; i < this.whereAnded.length; i++) {
sql += (i === 0 ? '' : ' and ');
sql += this.whereAnded[i].toSqlHana(context, parameter);
}
}
if (this.groupBys.length > 0) {
sql += '\ngroup by ';
sql += this.groupBys.map(function (groupBy) {
return groupBy.toSqlHana(context, parameter);
}).join(', ');
sql += ' ';
}
if (this.orders.length > 0) {
clonedContext = cloneContextWithNoAlias(context);
clonedContext.inOrderBy = true;
sql += '\norder by ';
for (i = 0; i < this.orders.length; i++) {
sql += (i === 0 ? '' : ', ');
sql += this.orders[i].toSqlHana(clonedContext, parameter);
}
sql += '';
}
if (this.limit !== null && this.limit >= 0 && this.offset) {
sql += ' limit ? offset ?';
parameter.push(this.limit);
parameter.push(this.offset);
} else if (this.offset) {
sql += ' limit null offset ?';
parameter.push(this.offset);
} else if (this.limit !== null && this.limit >= 0) {
sql += ' limit ?';
parameter.push(this.limit);
}
var hints = this.hints;
if (hints) {
if (hints.length > 0) {
sql += ' with hint (' + hints.join(',') + ')';
}
} else {
if (this.addCalcViewHint) {
// Disable calc view unfolding by default for older Hana/Calcview-Engine versions to avoid backward compatibility issues.
sql += ' with hint (NO_CALC_VIEW_UNFOLDING)';//due to fix in calcview engine
}
}
return sql;
};
/**
* Sets an indicator to add with "hint (NO_CALC_VIEW_UNFOLDING)" to the sql statment.
*/
Select.prototype.setAddCalcViewHint = function () {
this.addCalcViewHint = true;
};
/**
* Sets a list of hints
*/
Select.prototype.setHints = function (hints) {
this.hints = hints;
};
/**
* Set from clause
* SQL: select ... from <from>
* @param {{schema: string, table: string, alias: string}} from qualified tablename or
*/
Select.prototype.setFrom = function (from) {
return this.addFrom(from);
};
Select.prototype.addFrom = function (from, inputParams) {
function addPlaceholder(kv) {
function isStringArray(value) {
const defaultResult = { "isStringArray": false, "stringArray": [] };
// at least one string must be present in string array
if (value[0] !== '\'' || value[value.length - 1] !== '\'') {
return defaultResult;
}
//let localValue = value.substr(1, value.length - 2); // surrounding quotes from URI already removed by 'toSqlHana'
let localValue = value;
let stringArray = [];
let currString = "";
// simple state machine:
// 0 = initial
// 1 = start-quote
// 2 = end-quote
// 3 = in-string
// 4 = comma
let state = 0;
let ch = '';
for (let i = 0; i <= localValue.length - 1; i++) {
ch = localValue[i];
switch (state) {
case 0:
if (ch !== '\'') {
return defaultResult;
}
state = 1;
currString = "";
break;
case 1:
if (ch === '\'') {
state = 2;
currString = "";
} else {
currString = currString + ch;
state = 3;
}
break;
case 2:
if (ch === ',' && state === 2) {
state = 4;
} else {
return defaultResult;
}
break;
case 3:
if (ch === '\'' && ((i < localValue.length - 1 && localValue[i + 1] === ',')) || (i === localValue.length - 1)) {
stringArray.push(currString);
currString = "";
state = 2;
} else {
currString = currString + ch;
}
break;
case 4:
if (ch === '\'' && state === 4) {
state = 1;
} else {
return defaultResult;
}
break;
}
}
return { "isStringArray": (state === 2), "stringArray": stringArray };
}
from.placeholders = from.placeholders || [];
from.placeholders.push({
key: kv.key,
value: kv.value,
toSqlHana: function (context, params) {
var sql = util.format('placeholder."$$%s$$" => ?', kv.key); // placeholder."$$%s$$ == only for calc-views (not regular SQL)
var localParams = [];
var value;
if (kv.value instanceof edm.EdmString) {
//Edm.String
kv.value.toSqlHana(context, localParams);
value = localParams[0];
let result = isStringArray(value);
if (!result.isStringArray) {
value = value.replace(/\\/g, "\\\\");
value = value.replace(/'/g, "\\'");
} else {
let newStringArray = result.stringArray.map( element => {
element = element.replace(/\\/g, "\\\\");
element = element.replace(/'/g, "\\'");
return `'${element}'`;
});
value = newStringArray.join(',');
}
params.push(value); // e.g. "'a string'" or string-array
} else if (kv.value instanceof edm.Number) {
kv.value.toSqlHana(context, localParams);
if (typeof localParams[0] === "string") {
//Edm.Decimal(SMALLDECIMAL,DECIMAL),
params.push(localParams[0]); //e.g. "1.2"
} else {
//Edm.Byte(TINYINT), Edm.Int16(SMALLINT), Edm.Int32(INTEGER), Edm.Int64(BIGINT),
//Edm.Single(REAL,FLOAT), Edm.Double(DOUBLE)
params.push(localParams[0].toString()); //e.g. "1"
}
} else if (kv.value instanceof edm.Binary) {
value = kv.value.value;
value = value.substring(2, value.length - 1);
params.push(value);
//params.push(kv.value.value);
} else if (kv.value instanceof edm.DateTime) {
//Edm.DateTime(DATE,SECONDDATE,TIMESTAMP)
kv.value.toSqlHana(context, localParams);
params.push(localParams[0]); //e.g. "'1753-01-01'","'1753-01-01'","'1753-01-01'"
} else if (kv.value instanceof edm.Time) {
//Edm.Time(TIME)
kv.value.toSqlHana(context, localParams);
params.push(localParams[0]); //e.g. "'00:00:00'"
} else {
throw new InternalError('Navigation to calc view expected after an Input path segment 3');
}
return sql;
}
}
);
}
if (inputParams) {
inputParams.forEach(addPlaceholder);
}
this.froms.push(from);
return this;
}
;
/**
* Set from clause
* SQL: select ... from <from>
* @param {exports.Select} from qualified tablename or
* @param {string} [alias] qualified tablename or
*/
Select.prototype.setFromBySubQuery = function (from, alias) {
this.froms.push({ subQuery: from, alias: alias });
return this;
};
/**
* Set array of selected properties
* @param {(exports.Property|exports.SelectFormula)[]} select
*/
Select.prototype.setSelect = function (select) {
this.select = select;
};
/**
* Add array of selected properties
* @param {(exports.Property|exports.SelectFormula)[]} select
*/
Select.prototype.addSelect = function (select) {
this.select = this.select.concat(select);
return this;
};
Select.prototype.addSelects = function (newSelects) {
if (!Array.isArray(newSelects)) {
newSelects = [newSelects];
}
for (let newSelect of newSelects) {
let add = true;
if (newSelect.property) {
for (let select of this.select) {
if (newSelect.table === select.table &&
newSelect.property === select.property &&
newSelect.alias === select.alias) {
add = false;
}
}
}
if (add) {
this.select.push(newSelect);
}
}
return this;
};
Select.prototype.addUniqueSelects = function (selects) {
addUniqueSqlStatementsToList(selects, this.select);
return this;
};
/**
*
* @param {{key : exports.Property, value: string}[]} keyValuePairs
*/
Select.prototype.addWhereKeyValuePairs = function (keyValuePairs) {
for (var i = 0; i < keyValuePairs.length; i++) {
var kv = keyValuePairs[i];
this.whereAnded.push(new edm.BinaryOperator(edm.EQ, kv.key, kv.value, true /*strict*/));
}
return this;
};
/**
* @param {(edm.BinaryOperator|Object)} expression
*/
Select.prototype.addWhereAnded = function (expression) {
this.whereAnded.push(expression);
return this;
};
/**
* @param {edm.SortOrder[]} orders
*/
Select.prototype.addSortOrders = function (orders) {
for (var i = 0; i < orders.length; i++) {
this.orders.push(orders[i]);
}
return this;
};
Select.prototype.addGroupBys = function (groupBys) {
this.groupBys = this.groupBys.concat(groupBys);
return this;
};
Select.prototype.addSortOrder = function (order) {
this.orders.push(order);
return this;
};
/**
*
* @param {edm.OrderBy} orderBy
*/
Select.prototype.addOrderBy = function (orderBy) {
for (var i = 0; i < orderBy.orders.length; i++) {
this.orders.push(orderBy.orders[i]);
}
return this;
};
/**
*
* @param {edm.OrderBy} offset
*/
Select.prototype.addOffset = function (offset) {
this.offset = offset;
return this;
};
/**
*
* @param {edm.OrderBy} limit
*/
Select.prototype.addLimit = function (limit) {
this.limit = limit;
return this;
};
Select.prototype.toString = function () {
return this.toSqlHana({}, []);
};
Select.prototype.addFallbackStatement = function (statement) {
this.fallbackStatement = statement;
};
Select.prototype.getFallbackStatement = function () {
return this.fallbackStatement;
};
function Update() {
this.update = { schema: null, table: null, alias: null };
this.from = { schema: null, table: null, alias: null };
this.copyFromTo = [];//list of SelectProperty
this.whereAnded = [];
}
exports.Update = Update;
/**
* Set from clause
* SQL: select ... from <from>
* @param {{schema: string, table: string, alias: string}} table from qualified tablename
*/
Update.prototype.setTable = function (table) {
this.update = table;
};
Update.prototype.setFrom = function (from) {
this.from = from;
};
Update.prototype.addSetCopyProperties = function (properties, fromAlias) {
var max = properties.length;
var p;
for (var i = 0; i < max; i++) {
p = {
from: new SelectProperty(fromAlias, properties[i].property, null),
to: properties[i]
};
this.copyFromTo.push(p);
}
};
Update.prototype.addWhereKeyValuePairs = function (keyValuePairs) {
for (var i = 0; i < keyValuePairs.length; i++) {
var kv = keyValuePairs[i];
this.whereAnded.push(new edm.BinaryOperator(edm.EQ, kv.key, kv.value, true /*strict*/));
}
};
Update.prototype.toSqlHana = function (context, parameter) {
// 'context.isHanaCloudDb': is set in SQL context (hence 'context' here) to member 'isHanaCloudDb';
// check creation of 'new SqlBuildHanaContext' above
let isHanaCloudDb = (context.isHanaCloudDb) ? context.isHanaCloudDb : true; // Hana Cloud as default
return (isHanaCloudDb) ? this.getHANACloudUpdateSql(context, parameter) : this.getHANAServiceUpdateSql(context, parameter);
};
Update.prototype.getHANACloudUpdateSql = function(context, parameter) {
var sql = '';
sql += 'MERGE INTO ';
if (this.update.table.substr(0, 1) === '#') {
sql += ' "' + this.update.table + '" ';
if (this.update.alias) {
sql += ' "' + this.update.alias + '"';
}
} else {
if (this.update.schema) {
sql += '"' + this.update.schema + '".';
}
sql += '"' + this.update.table + '"';
if (this.update.alias) {
sql += ' "' + this.update.alias + '"';
}
}
sql += ' USING ';
if (this.from.table.substr(0, 1) === '#') {
sql += ' "' + this.from.table + '" ';
if (this.from.alias) {
sql += ' "' + this.from.alias + '"';
}
} else {
if (this.from.schema) {
sql += '"' + this.from.schema + '".';
}
sql += '"' + this.from.table + '"';
if (this.from.alias) {
sql += ' "' + this.from.alias + '"';
}
}
if (this.whereAnded.length > 0) {
sql += ' ON ';
for (let i = 0; i < this.whereAnded.length; i++) {
sql += (i === 0 ? '' : ' and ');
sql += '(';
sql += this.whereAnded[i].toSqlHana(context, parameter);
sql += ')';
}
}
sql += ' WHEN MATCHED THEN UPDATE ';
sql += ' SET ';
for (let i = 0; i < this.copyFromTo.length; i++) {
var cft = this.copyFromTo[i];
sql += (i === 0 ? '' : ', ');
sql += cft.to.toSqlHana(context, parameter, { withoutTable: true });
sql += '=';
sql += cft.from.toSqlHana(context, parameter);
}
return sql;
};
Update.prototype.getHANAServiceUpdateSql = function(context, parameter) {
var i;
var sql = '';
sql += 'update ';
if (this.update.table.substr(0, 1) === '#') {
sql += ' "' + this.update.table + '" ';
if (this.update.alias) {
sql += ' "' + this.update.alias + '"';
}
} else {
if (this.update.schema) {
sql += '"' + this.update.schema + '".';
}
sql += '"' + this.update.table + '"';
if (this.update.alias) {
sql += ' "' + this.update.alias + '"';
}
}
sql += ' set ';
for (i = 0; i < this.copyFromTo.length; i++) {
var cft = this.copyFromTo[i];
sql += (i === 0 ? '' : ', ');
sql += cft.to.toSqlHana(context, parameter, { withoutTable: true });
sql += '=';
sql += cft.from.toSqlHana(context, parameter);
}
// from <origin table> <origin alias> , <temp table> <temp alias>
sql += '\nfrom ';
if (this.update.schema) {
sql += '"' + this.update.schema + '".';
}
sql += ' "' + this.update.table + '" ';
if (this.update.alias) {
sql += ' "' + this.update.alias + '"';
}
if (this.from.table.substr(0, 1) === '#') {
sql += ' , ';
sql += ' "' + this.from.table + '" ';
if (this.from.alias) {
sql += ' "' + this.from.alias + '"';
}
} else {
if (this.from.schema) {
sql += '"' + this.from.schema + '".';
}
sql += '"' + this.from.table + '"';
if (this.from.alias) {
sql += ' "' + this.from.alias + '"';
}
}
if (this.whereAnded.length > 0) {
sql += '\n where ';
for (i = 0; i < this.whereAnded.length; i++) {
sql += (i === 0 ? '' : ' and ');
sql += '(';
sql += this.whereAnded[i].toSqlHana(context, parameter);
sql += ')';
}
}
return sql;
};
/**
* @class Upsert
* @constructor
*/
function Upsert() {
this.upsert = { schema: null, table: null, alias: null };
this.subSelect = null;
this.nv = [];
this.whereAnded = [];
}
exports.Upsert = Upsert;
Upsert.prototype.setTable = function (table) {
this.upsert = table;
};
Upsert.prototype.setSubSelect = function (subSelect) {
this.subSelect = subSelect;
};
Upsert.prototype.addNames = function (names) {
for (var i = 0; i < names.length; i++) {
var n = names[i];
this.nv.push({
name: n,
value: undefined //null would be a valid OData value
});
}
};
Upsert.prototype.setValue = function (name, value) {
for (var i = 0; i < this.nv.length; i++) {
var nv = this.nv[i];
if (nv.name.property === name) {
nv.value = value;
}
}
};
Upsert.prototype.addWhereKeyValuePairs = function (keyValuePairs) {
for (var i = 0; i < keyValuePairs.length; i++) {
var kv = keyValuePairs[i];
this.whereAnded.push(new edm.BinaryOperator(edm.EQ, kv.key, kv.value, true /*strict*/));
}
};
Upsert.prototype.toSqlHana = function (context, parameter) {
var i;
var sql = '';
sql += 'upsert ';
if (this.upsert.table.substr(0, 1) === '#') {
sql += ' "' + this.upsert.table + '" ';
if (this.upsert.alias) {
sql += ' "' + this.upsert.alias + '"';
}
} else {
if (this.upsert.schema) {
sql += '"' + this.upsert.schema + '".';
}
sql += '"' + this.upsert.table + '"';
if (this.upsert.alias) {
sql += ' "' + this.upsert.alias + '"';
}
}
var tmp = '';
if (this.subSelect) {
//sql += ' (';
sql += ' ' + this.subSelect.toSqlHana(context, parameter);
//sql += ') ';
} else {
sql += ' values ';
tmp = '';
for (i = 0; i < this.nv.length; i++) {
tmp += (tmp.length > 0 ? ', ' : '');
if (this.nv[i].value && this.nv[i].value.toSqlHana) {
var pleaceholder = [];
this.nv[i].value.toSqlHana(context, pleaceholder, { withoutTable: true });
tmp += this.nv[i].value.toSqlHana(context, parameter, { withoutTable: true });
} else {
tmp += '?';
parameter.push(this.nv[i].value);
}
}
sql += '(' + tmp + ')';
//collect values
}
if (this.whereAnded.length > 0) {
sql += '\n where ';
for (i = 0; i < this.whereAnded.length; i++) {
sql += (i === 0 ? '' : ' and ');
sql += '(';
sql += this.whereAnded[i].toSqlHana(context, parameter);
sql += ')';
}
}
return sql;
};
/*------------------------------*/
/**
* Creates following structure on given target object:
*
* target.table: {
* name: nameParameter,
* schema: schemaParameter,
*}
*
*@param {Object} target The target object to create structure on
*@param {String} tableName The table name
*@param {String} [schema] The name of the schema
*@throws {Error} throws an error if tableName was not provided
*@private
*/
function initializeDDLObject(target, tableName, schema) {
if (!tableName) {
throw new Error("Can not create object without 'tableName' parameter");
}
target.table = {
name: tableName,
schema: schema
};
}
/**
* Creates a simple prefixed sql like '<prefix> TABLE "schema"."table"'.
*
*@param {Object} options Options object with following structure:
* {prefix:..., schema: ..., table: ...} where schema is optional
*@private
*/
function buildSimplePrefixedSql(options) {
var sql = options.prefix + " TABLE ";
if (options.schema) {
sql += '"' + options.schema + '".';
}
sql += '"' + options.table + '"';
return sql;
}
/**
* Creates a drop table sql statement object.
*
*@param {string} tableName The table name to drop
*@param {string} [schema] The name of the schema
*/
function Drop(tableName, schema) {
initializeDDLObject(this, tableName, schema);
}
exports.Drop = Drop;
/**
* Creates the correspondig hana drop sql statement string which is represented by this object.
*
*@returns {string} The generated sql string
*/
Drop.prototype.toSqlHana = function () {
return buildSimplePrefixedSql({
prefix: "DROP",
schema: this.table.schema,
table: this.table.name
});
};
/**
* Creates a truncate table sql statement object.
*
*@param {string} tableName The table name to truncate
*@param {string} [schema] The name of the schema
*/
function Truncate(tableName, schema) {
initializeDDLObject(this, tableName, schema);
}
exports.Truncate = Truncate;
/**
* Creates the correspondig hana truncate sql statement string which is represented by this object.
*
*@returns {string} The generated sql string
*/
Truncate.prototype.toSqlHana = function () {
return buildSimplePrefixedSql({
prefix: "TRUNCATE",
schema: this.table.schema,
table: this.table.name
});
};
/*------------------------------*/
function Delete() {
this.from = { schema: null, table: null, alias: null };
this.whereAnded = [];
}
exports.Delete = Delete;
Delete.prototype.setFrom = function (from) {
this.from = from;
};
Delete.prototype.addWhereKeyValuePairs = function (keyValuePairs) {
for (var i = 0; i < keyValuePairs.length; i++) {
var kv = keyValuePairs[i];
this.whereAnded.push(new edm.BinaryOperator(edm.EQ, kv.key, kv.value, true /*strict*/));
}
};
Delete.prototype.toSqlHana = function (context, parameter) {
var i = 0;
var sql = '';
sql += 'delete ';
sql += 'from ';
if (this.from.table.substr(0, 1) === '#') {
sql += '' + this.from.table + '';
if (this.from.alias) {
sql += ' as "' + this.from.alias + '"';
}
} else {
if (this.from.schema) {
sql += '"' + this.from.schema + '".';
}
sql += '"' + this.from.table + '"';
if (this.from.alias) {
sql += ' as "' + this.from.alias + '"';
}
}
if (this.whereAnded.length > 0) {
sql += '\n where ';
for (i = 0; i < this.whereAnded.length; i++) {
sql += (i === 0 ? '' : ' and ');
sql += this.whereAnded[i].toSqlHana(context, parameter);
}
}
return sql;
};
function addUniqueSqlStatementsToList(newStatements, list) {
newStatements = [].concat(newStatements);
newStatements.forEach(function (newStatement) {
if (list.every(notEqual.bind(null, newStatement))) {
list.push(newStatement);
}
});
function notEqual(prop, current) {
return prop.toSqlHana({}, []) !== current.toSqlHana({}, []);
}
}
exports._addETagToSelect = addETagToSelect;
/**
* Adds ETag generation expression to the SQL SELECT statement as an pseudo "__etag" column in the result set.
* @param {object} dbSegment - DBSegment, which represents entity type of the entity, for which ETag should be generated
* @param {object} selectStmt - SQL SELECT statement, where the ETag expression should be added to the SELECT part
* @param {string} [tableAlias] - DB table alias. The parameter should be specified only if the alias of the table,
* which is used in the SELECT statement is different than the Alias property of the DBSegment (i.e. default alias).
*/
function addETagToSelect(dbSegment, selectStmt, tableAlias) {
var concurrentProperties = dbSegment.entityType.getConcurrentProperties(),
hashFunctionArguments,
eTagExpression;
tableAlias = tableAlias || dbSegment.getAlias();
hashFunctionArguments = concurrentProperties.map(function (property) {
var dbColumnName = util.format('"%s"."%s"', tableAlias, property);
return util.format("ifnull(to_binary(%s), to_binary('')), case when %s is null then '01' else '00' end",
dbColumnName, dbColumnName);
});
eTagExpression = "to_varchar(hash_sha256(" + hashFunctionArguments.join(",") + "))";
selectStmt.addSelects(new SelectFormula(null, eTagExpression, "__etag"));
}
exports.createSelectStmtForETag = createSelectStmtForETag;
/**
* Creates SELECT statement to generate ETag for the entity.
*
* @param {object} dbSegment - DBSegment, which represents the entity type of the entity, for which ETag should be
* generated.
* @returns {Select}
*/
function createSelectStmtForETag(dbSegment) {
var selectStmt = new Select();
addETagToSelect(dbSegment, selectStmt);
selectStmt.addFrom(dbSegment.getAliasedTableName());
selectStmt.addWhereKeyValuePairs(dbSegment.getQKeyWithValues());
return selectStmt;
}
/**
* Builds table statement object. The type of object created depends on the type parameter.
* Currently supported is Truncate, Drop.
* Examples:
* <code>
* var sql = require("lib/.../sqlStatement");
* var truncate = sql.buildTableStatement(sql.Truncate, sqlContext, "yourTableName");
* var drop = sql.buildTableStatement(sql.Drop, sqlContext, "yourTableName");
* </code>
*
*@param type {Function} The type of statement you wnat to create
*@param sqlContext {Object} The current sql context
*@param tableName {String} The name of the table to operate on
*@param schema {String} The schema of the table to operate on
*@returns {Object} Object representation of sql statement
*/
function buildTableStatement(type, sqlContext, tableName, schema) {
sqlContext.context.logger.debug('sqlStatement', 'buildTableStatement');
if (type === exports.Truncate) {
return new exports.Truncate(tableName, schema);
}
if (type === exports.Drop) {
return new exports.Drop(tableName, schema);
}
return null;
}
exports.buildTableStatement = buildTableStatement;
/**
* Object representing the DB table column in the SQL query (except of the SELECT queries, where SelectProperty class
* should be used).
* @param {string} table - DB table name
* @param {string} property - DB column name
* @param {?string=} alias - alias, which should be used for the DB column in the SQL query
* @constructor
*/
function TableColumn(table, property, alias) {
this.table = table;
this.property = property;
this.alias = alias;
}
exports.TableColumn = TableColumn;
TableColumn.prototype.toSqlHana = function (context, parameter, hint) {
var sql = [];
if (!(hint && hint.withoutTable) && this.table) {
sql.push('"', this.table, '".');
}
sql.push('"', this.property, '"');
if (this.alias) {
sql.push(' "', this.alias, '"');
}
return sql.join("");
};