@sap/xsodata
Version:
Expose data from a HANA database as OData V2 service with help of .xsodata files.
1,432 lines (1,212 loc) • 57.8 kB
JavaScript
'use strict';
let _ = require('lodash');
const getCollector = require('../sql/dataCollectorGet');
const sqlStatement = require('./../sql/sqlStatement');
const dbConnect = require('./../db/connect');
const nodeUtils = require('util');
const xsODataUtils = require('../utils/utils');
const typedObjects = require('./../utils/typedObjects');
const EntityType = require('../model/entityType.js');
const typeConverter = require('./../utils/typeConverter');
const keyGenerator = require('../utils/keyGenerator');
const BadRequest = require('../utils/errors/http/badRequest');
const InternalError = require('../utils/errors/internalError');
// Types of db segments
exports.DBS_Entity = 2;
exports.DBS_ResourceNavigation = 3;
exports.DBS_Property = 4;
exports.DBS_Navigation = 6; // expanded navigation || selected navigation
exports.DbSegment = DbSegment;
/**
* Constructs a dbSegment. A dbSegment stores all information to load data from one actual database table
*
* @param kind Either Entity, Navigation in resource path, property or navigation in expand tree
* @param entityType
* @param nr Id used for creating temporary table with unique names
* @constructor
*/
function DbSegment(kind, entityType, nr) {
this.kind = kind;
this.previousDBSegment = null;
this.entityType = entityType;
// restrictions used on last segment of resource path
this.restriction = {
onlyCount: false, // $count used in uri
onlyRefs: false, // $ref used in uri
onlyValue: false // $value used in uri
};
this.isLinks = false;
this.isCollection = undefined;
this.singleProperty = null;
this._end = { from: null, to: null };
//for SQL generation
this._DB_Schema = entityType.schema;
this._DB_TableName = entityType.tableName;
this._AliasName = entityType.name;
this._AliasId = nr;
this._Alias = this._AliasName + this._AliasId;
/**
* List of key predicates
* @type {Array<{key : string,value : string}>}
* @private
*/
this._KeyValues = [];
this._recordNV = [];
/*use for post/put/delete*/
this._recordMapFromPayload = {};
/**
* Input parameters if this dbSegment queries a calcview
* @type {Array<{key : string,value : string}>}
* @private
*/
this._InputParams = [];
this._InputParamsMap = new Map();
/**
* Selected property name (either by * all properties of the entity set or explicit via $select)
* @type {Array<String>}
*/
this._SelectedProperties = [];
/**
* Store column names ordered
* @type {Array<String>}
*/
this._SelectedPropertiesOrdered = null;
/**
* For CV: (real) key names with alias
* @type {Array<String,string>}
*/
this._aliasedKeyPropertiesOnCalcView = [];
/**
* Store names existing of navigation properties
* @type {Array<String>}
*/
this._SelectedNavigations = []; //Array of string
/**
* Store names of really expanded of navigation properties
* @type {Array<String>}
*/
this._ExpandedNavigations = []; //Array of string
/**
* Store dbSegments of really expanded of navigation properties
* @type {Map<String,DbSegment>}
*/
this._ExpandedNavigationsDBSeg = {}; //Array of DbSegments
/**
* Store dbSegments of used navigation properties. May contain more navigation properties than
* attribute _ExpandedNavigationsDBSeg
* @type {Map<String,DbSegment>}
*/
this._relevantNavigationSegments = {};
this.isGenKeySelected = false;
this.systemQueryParameter = {
filter: null,
orderBy: null,
top: null,
skip: null
};
this.sql = {
stmContainer: null,
rows: null,
rowsInput: null,
readPosition: 0
};
/**
* @type {null}
*/
this._rowsWithGenKey = null;
this.nextDBSegment = null;
this.association = null;
}
/**
* Return only the alias for the table
* @returns {string} Alias used to reference the table
*/
DbSegment.prototype.getAlias = function () {
return this._Alias;
};
/**
* Return only the alias for an association
* @returns {string} Alias used to reference the association
*/
DbSegment.prototype.getAssocAlias = function () {
return 'Assoc' + this._AliasId;
};
/**
* Returns the tableName with schema and alias
* @returns {{schema: string, table: string, alias: string}}
*/
DbSegment.prototype.getAliasedTableName = function (alias) {
return {
schema: this._DB_Schema,
table: this._DB_TableName,
alias: alias || this._Alias
};
};
/**
* Check if the given dbSegment navigates the corresponding association from its principal end
* @returns {boolean}
* */
DbSegment.prototype.isPrincipal = function () {
return this.entityType.name === this.association.principal.type;
};
/**
* Returns the key values of the DbSegment. The key values origin from the URL parameters (POST/PUT)
* @param key
* @returns {undefined|{key: string, value: string}}
*/
DbSegment.prototype.getKeyValue = function (key) {
for (let i = 0; i < this._KeyValues.length; i++) {
if (this._KeyValues[i].name === key) {
return this._KeyValues[i];
}
}
return undefined;
};
DbSegment.prototype.hasKeyValues = function () {
return this._KeyValues.length > 0;
};
/**
* Move POST http payload into this._recordNv and this._recordMapFromPayload
* Key/Inputparameter Values are also filled
* If required created/modified by/at information is also added
* this._recordNv is used to fill the SQL statements
* @param context
* @param record Data from http payload
*/
DbSegment.prototype.setRecordFromPostPayload = function (context, record) {
const converterArray = typeConverter.converterFunctions.jsonPayloadToDbName;
// const converterArray1 = typeConverter.converterFunctions.uriPayloadToDbName;
const supportNullable = context.gModel.isNullSupported();
const createBy = this.entityType.getAddAdmindata('create', 'by');
const createAt = this.entityType.getAddAdmindata('create', 'at');
const modifyBy = this.entityType.getAddAdmindata('modify', 'by');
const modifyAt = this.entityType.getAddAdmindata('modify', 'at');
//Loop through the properties of the entityType.
//Object.keys(this.entityType.propertiesMap).forEach(function (property) {
for (let [key, prop] of Object.entries(this.entityType.propertiesMap)) {
//Load property from record
const value = record[key];
let dbValue;
if (value === undefined) {
// Use columns default value
if (prop.KIND === EntityType.entityKind.inputParameters) {
if (this._InputParamsMap.has(key)) {
const dbType = this.entityType.propertiesMap[key]['DATA_TYPE_NAME'];
let param = [];
this._InputParamsMap.get(key).toSqlHana(context, param, { useDbType: dbType });
dbValue = param[0];
} else {
if (context.request.method === 'GET') {
//for create, update, delete on a calcview input parameters are optional
throw new BadRequest('Payload for input parameter ' + key + ' missing');
}
}
} else {
dbValue = this.entityType.propertiesMap[key]['DEFAULT_VALUE'];
}
} else if (value === null) {
// Use null
const isNullable = this.entityType.propertiesMap[key]['IS_NULLABLE'] === 'TRUE';
if (!supportNullable || !isNullable) {
if (!((createBy && key === 'CREATED_BY') ||
(createAt && key === 'CREATED_AT') ||
(modifyBy && key === 'MODIFIED_BY') ||
(modifyAt && key === 'MODIFIED_AT'))
) {
throw new BadRequest('The serialized resource has a missing value for member ' + key);
}
}
dbValue = null;
} else {
if (this.entityType._entityType.parameters && this.entityType._entityType.parameters.viaKey === true) {
if (this._InputParamsMap.get(key)) {
throw new BadRequest('Inputparameter ' + key + ' must be provided only in uri (POST)');
}
if (prop.KIND === EntityType.entityKind.inputParameters) {
throw new BadRequest('Inputparameter ' + key + ' must be provided in uri (POST)');
}
}
//Use the converted value
const dbType = this.entityType.propertiesMap[key]['DATA_TYPE_NAME'];
const converter = converterArray[typeConverter.dbTypeNameToODataTypeName[dbType]];
dbValue = converter(value, dbType);
}
this._recordMapFromPayload[key] = dbValue;
this._recordNV.push({ name: key, value: dbValue });
}
//set KeyValues
let max = this.entityType.keys.names.length;
for (let i = 0; i < max; i++) {
this._KeyValues.push(
{
name: this.entityType.keys.names[i],
value: this._recordMapFromPayload[this.entityType.keys.names[i]],
dbType: this.entityType.propertiesMap[this.entityType.keys.names[i]].DATA_TYPE_NAME // OK
}
);
}
// Here a check that no additional properties (and also no inline content) are send to server might be added
};
/**
* Move PUT http payload into this._recordNv and this._recordMapFromPayload
* Key/Inputparameter Values are also filled
* If required created/modified by/at information is also added
* this._recordNv is used to fill the SQL statements
* @param context
* @param record Data from http payload
*/
DbSegment.prototype.setRecordFromPutPayload = function (context, record) {
const converterArray = typeConverter.converterFunctions.jsonPayloadToDbName;
const supportNullable = context.gModel.isNullSupported();
const createBy = this.entityType.getAddAdmindata('create', 'by');
const createAt = this.entityType.getAddAdmindata('create', 'at');
const modifyBy = this.entityType.getAddAdmindata('modify', 'by');
const modifyAt = this.entityType.getAddAdmindata('modify', 'at');
//Check if all required properties are send to server
Object.keys(this.entityType.propertiesMap).forEach(function (property) {
let dbValue;
let kv = this.getKeyValue(property);
if (kv) {
if (this.entityType._entityType.parameters && this.entityType._entityType.parameters.viaKey === true) {
if (this._InputParamsMap.get(property) && record[property]) {
throw new BadRequest('Inputparameter ' + property + ' must be provided only in uri (PUT)');
}
}
dbValue = kv.value;
this._recordMapFromPayload[property] = dbValue;
this._recordNV.push({ name: property, value: dbValue });
} else {
const value = record[property];
const isNullable = this.entityType.propertiesMap[property]['IS_NULLABLE'] === 'TRUE';
dbValue = null;
if (value === undefined) {
if (supportNullable && isNullable) {
dbValue = null;
} else {
if (!(
// skip special properties
(createBy && property === 'CREATED_BY') ||
(createAt && property === 'CREATED_AT') ||
(modifyBy && property === 'MODIFIED_BY') ||
(modifyAt && property === 'MODIFIED_AT') ||
// skip optional calc view parameters
(this.entityType.propertiesMap[property] && this.entityType.propertiesMap[property].MANDATORY === 0)
)) {
throw new BadRequest('The serialized resource has a missing value for member ' + property);
}
}
} else {
const dbType = this.entityType.propertiesMap[property]['DATA_TYPE_NAME'];
dbValue = converterArray[typeConverter.dbTypeNameToODataTypeName[dbType]](value, dbType);
}
this._recordMapFromPayload[property] = dbValue;
this._recordNV.push({ name: property, value: dbValue });
}
}.bind(this));
// Here a check that no additional properties (and also no inline content) are send to server might be added
};
DbSegment.setRecordPutPostLinks = function (context) {
let toBeUpdated = context.oData.links.toBeUpdated;
let keySource = context.oData.links.keySource;
// Add keys
toBeUpdated._KeyValues.forEach(function (key) {
toBeUpdated._recordMapFromPayload[key.name] = key.value;
toBeUpdated._recordNV.push({ name: key.name, value: key.value });
});
// Add foreign keys, e.g. Employees.ManagerId
let fkeys = toBeUpdated.getQForeignKeyProperties();
fkeys.forEach(function (key, i) {
toBeUpdated._recordMapFromPayload[key.property] = keySource._KeyValues[i].value;
toBeUpdated._recordNV.push({ name: key.property, value: keySource._KeyValues[i].value });
});
};
DbSegment.setRecordDeleteLinks = function (context) {
let toBeUpdated = context.oData.links.toBeUpdated;
// Add keys
toBeUpdated._KeyValues.forEach(function (key) {
toBeUpdated._recordMapFromPayload[key.name] = key.value;
toBeUpdated._recordNV.push({ name: key.name, value: key.value });
});
// Skip adding foreign keys, so that later in movePayloadFromSegmentToSelectStm they are set to 'null'
};
/**
* Returns the tableName with schema and alias
* @returns {{schema: string, table: string}}
*/
DbSegment.prototype.getQTableNameNoAlias = function () {
return {
schema: this._DB_Schema,
table: this._DB_TableName
};
};
/**
* @returns {{type: string, joinProperties: [string], multiplicity: string}|null}
*/
DbSegment.prototype.getTo = function () {
return this._end.to;
};
/**
* @param {{type: string, joinProperties: [string], multiplicity: string}} endTo
*/
DbSegment.prototype.setTo = function (endTo) {
this._end.to = endTo;
};
/**
* @param {{type: string, joinpProperties: [string], multiplicity: string}} endFrom
*/
DbSegment.prototype.setFrom = function (endFrom) {
this._end.from = endFrom;
};
/**
* @returns {{type: string, joinProperties: [string], multiplicity: string}}
*/
DbSegment.prototype.getFrom = function () {
return this._end.from;
};
DbSegment.prototype.setOver = function (endOver) {
this._end.over = endOver;
};
DbSegment.prototype.getOver = function () {
return this._end.over;
};
DbSegment.prototype.setLinks = function () {
if (this.previousDBSegment) {
this.previousDBSegment.setLinks();
} else {
this.isLinks = true;
}
};
/**
* Returns the key names as TableColumn array for usage in the INSERT, UPDATE or CREATE TABLE SQL statements.
* @returns {sql.TableColumn[]}
*/
DbSegment.prototype.getQKeyProperties = function () {
let ret = [];
for (let i = 0; i < this.entityType.keys.names.length; i++) {
ret.push(new sqlStatement.TableColumn(this._Alias, this.entityType.keys.names[i]));
}
return ret;
};
/**
* Returns the key names as SelectProperty array for usage in a SELECT SQL statement.
* @returns {sqlStatement.SelectProperty[]}
*/
DbSegment.prototype.getQKeyPropertiesForSelect = function () {
return this._createSelectProperties(this._Alias, this.entityType.keys.names);
};
/**
* Creates an array of SelectProperty instances for usage in a SELECT SQL statement.
* @returns {sqlStatement.SelectProperty[]}
*/
DbSegment.prototype._createSelectProperties = function (tableAlias, propertyNames, with0123Alias) {
let propertiesMap = this.entityType.propertiesMap;
return propertyNames.map(function (propertyName, index) {
let property = propertiesMap[propertyName];
let propertyType = property.aggregate ? null : property.DATA_TYPE_NAME;
return new sqlStatement.SelectProperty(tableAlias, propertyName, propertyType,
with0123Alias ? index.toString() : undefined);
});
};
DbSegment.prototype.getQKeyPropertiesWith0123AliasForSelect = function (noTable) {
return this._createSelectProperties(noTable ? null : this._Alias, this.entityType.keys.names, true);
};
DbSegment.prototype.getKeyProperties0123ForSelect = function (noTable) {
let ret = [];
let keyNames = this.entityType.keyNamesOrdered;
for (let i = 0; i < keyNames.length; i++) {
ret.push(
new sqlStatement.SelectProperty(noTable ? null : this._Alias, i.toString(), null)
);
}
return ret;
};
/**
* Adds a table input parameter when accessing a calcview with localVariables.
* E.g. OrderValues.hdbcalculationview with IP_LANG and IP_CLIENT
* Call via: http://.../material.xsodata/OrderValueParameters(IP_CLIENT='100',IP_LANG='E')/Results?$format=json
*
* @param context
* @param key
* @param value
*/
DbSegment.prototype.addInputParameter = function (context, key, value) {
context.logger.silly('dbsegment', 'addInputParameter');
this._InputParams.push({ key: key, value: value });
this._InputParamsMap.set(key, value);
};
DbSegment.prototype.getInputParameters = function () {
return this._InputParams;
};
DbSegment.prototype.getParameterValue = function (parameterName) {
let param = _.find(this._InputParams, function (kv) {
return kv.key === parameterName;
});
if (param) {
return param.value;
}
};
DbSegment.prototype.validateInputParameters = function () {
for (let [keyName, value] of Object.entries(this.entityType.inputParameters)) {
if (value.MANDATORY === 1 && !this._InputParamsMap.has(keyName)) {
throw new InternalError('Missing value for input parameter ' + keyName + '.');
}
}
for (let i = 0; i < this._InputParams.length; i++) {
let keyName = this._InputParams[i].key;
if (!this.entityType.inputParameters[keyName]) {
throw new InternalError('Inputparameter ' + keyName + ' unknown');
}
}
};
DbSegment.prototype.validateKeyParameters = function () {
const keyNames = this.entityType.keyNamesOrdered;
for (let i = 0; i < keyNames.length; i++) {
const keyName = keyNames[i];
const property = this.entityType.propertiesMap[keyName];
if (property.KIND !== EntityType.entityKind.inputParameters) {
if (!this._InputParamsMap.has(keyName)) {
throw new InternalError('Missing value for input parameter ' + keyName + '.');
}
}
}
};
/**
* Create a sql select fragments to select key properties with alias 0,1,2, ...
* Those selects are used for $expand to join the sub queries
* Sample: ... "FromEntity1"."KEY_1" "0" ...
* @param noTable Provide true if the table should be added to the fragment
* @returns {[]}
*
*/
DbSegment.prototype.getKeyProperties0123ForSelectAs0123 = function (noTable) {
let ret = [];
let keyNames;
let keyName;
let property;
let selectProperty;
let propertyType;
let paramValue;
let i;
keyNames = this.entityType.keyNamesOrdered;
for (i = 0; i < keyNames.length; i++) {
keyName = keyNames[i];
property = this.entityType.propertiesMap[keyName];
selectProperty = null;
if (property.KIND === EntityType.entityKind.inputParameters) {
paramValue = this.getParameterValue(property.COLUMN_NAME);
selectProperty = new sqlStatement.ParameterSelectProperty(noTable ? null : this._Alias, keyName, i.toString(), paramValue);
// Here the value is added.
// E.g. for /CalcFrom(IN_INTEGER=123,IN_NVARCHAR='AAA',KEY_2=24) there is no way to do a
// select IN_INTEGER, IN_NVARCHAR from CalcFrom(placeholder."$$IN_NVARCHAR$$" => ?,placeholder."$$IN_INTEGER$$" => ?) where ("CalcFrom1"."KEY_2" = ?) with ? = [123, 'AAA', 24]
// that because the calcview at HANA object does not expose the inputparameter in the result set.
// So the input parameters are only added as key in the OData layer.
} else {
propertyType = property.aggregate ? null : property.DATA_TYPE_NAME;
selectProperty = new sqlStatement.SelectProperty(noTable ? null : this._Alias, keyName, propertyType, i.toString());
}
ret.push(selectProperty);
}
return ret;
};
DbSegment.prototype.getKeyProperties0123ForSelectOnCalcViewAs0123 = function (noTable) {
let ret = [];
let keyNames;
let keyName;
let property;
let selectProperty;
let propertyType;
let i;
let j = -1;
keyNames = this.entityType.keyNamesOrdered;
for (i = 0; i < keyNames.length; i++) {
keyName = keyNames[i];
property = this.entityType.propertiesMap[keyName];
selectProperty = null;
if (property.KIND !== EntityType.entityKind.inputParameters) {
j = j + 1; // for real key properties use numbering: 0,1,2, ... (skip CV input params)
propertyType = property.aggregate ? null : property.DATA_TYPE_NAME;
selectProperty = new sqlStatement.SelectProperty(noTable ? null : this._Alias, keyName, propertyType, j.toString());
ret.push(selectProperty);
}
}
return ret;
};
DbSegment.prototype.setAliasedKeyPropertiesOnCalcView = function(selectProperties) {
this._aliasedKeyPropertiesOnCalcView = selectProperties;
};
DbSegment.prototype.hasAliasedKeyPropertiesOnCalcView = function() {
return this._aliasedKeyPropertiesOnCalcView.length !== 0;
};
DbSegment.prototype.getAliasedKeyPropertyOnCalcView = function(propertyName) {
for (let i = 0; i < this._aliasedKeyPropertiesOnCalcView.length; i++) {
if (this._aliasedKeyPropertiesOnCalcView[i].property === propertyName) {
return this._aliasedKeyPropertiesOnCalcView[i];
}
}
return null;
};
/**
* Returns a selectProperty for usage in the select part of sql statements
* The property are named "0","1","2",... and have as type the type of the corresponding key ( ordered by position
* @returns {sqlStatement.SelectProperty[]}
*/
DbSegment.prototype.getKeyProperties0123ForCreate = function () {
let ret = [];
let keyNames = this.entityType.keyNamesOrdered;
let propertiesMap = this.entityType.propertiesMap;
for (let i = 0; i < keyNames.length; i++) {
let keyName = keyNames[i];
let property = propertiesMap[keyName];
let typeString = property.DATA_TYPE_NAME;
if (typeString === 'DECIMAL') {
typeString += '(' + property.LENGTH.toString() + ',' + property.SCALE.toString() + ')';
} else if (typeString === 'VARCHAR') {
typeString += '(' + property.LENGTH.toString() + ')';
} else if (typeString === 'NVARCHAR') {
typeString += '(' + property.LENGTH.toString() + ')';
} else if (typeString === 'ALPHANUM') {
typeString += '(' + property.LENGTH.toString() + ')';
} else if (typeString === 'CHAR') {
typeString += '(' + property.LENGTH.toString() + ')';
} else if (typeString === 'NCHAR') {
typeString += '(' + property.LENGTH.toString() + ')';
} else if (typeString === 'VARBINARY') {
typeString += '(' + property.LENGTH.toString() + ')';
} else if (typeString === 'SHORTTEXT') {
typeString = 'NVARCHAR(10)';
}
ret.push(
new sqlStatement.CreateProperty(i.toString(), typeString)
);
}
return ret;
};
DbSegment.prototype.getKeyProperties0123ForOrderBy = function (noTable) {
let ret = [];
let keyNames = this.entityType.keyNamesOrdered;
for (let i = 0; i < keyNames.length; i++) {
ret.push(new typedObjects.SortOrder(
/* no dbType required as property is used only for SortOrder */
new typedObjects.Property(i.toString(), noTable ? null : this._Alias), 'ASC'
));
}
return ret;
};
DbSegment.prototype.getKeyProperties0123ForOrderByCalcView = function (noTable) {
let ret = [];
let keyName;
let property;
let j = -1;
let keyNames = this.entityType.keyNamesOrdered;
for (let i = 0; i < keyNames.length; i++) {
keyName = keyNames[i];
property = this.entityType.propertiesMap[keyName];
if (property.KIND !== EntityType.entityKind.inputParameters) {
j = j + 1;
let newSortOrder = new typedObjects.SortOrder(new typedObjects.Property(j.toString(), noTable ? null : this._Alias), 'ASC');
newSortOrder.setPropertyName(property.COLUMN_NAME);
ret.push(newSortOrder);
}
}
return ret;
};
DbSegment.prototype.getKeyPropertiesNotSelectedForSelect = function (noTable = undefined, inputParameters = null) {
let ret = [];
let keyNames;
let i;
let keyName;
let key;
let propertyType;
keyNames = this.entityType.keyNamesOrdered;
// remove input params from key list
if (inputParameters !== null) {
for (let inputParameterName in inputParameters) {
if (inputParameters.hasOwnProperty(inputParameterName)) {
let indexKeyName = keyNames.indexOf(inputParameterName);
if (indexKeyName > -1) {
keyNames.splice(indexKeyName, 1);
}
}
}
}
for (i = 0; i < keyNames.length; i++) {
keyName = keyNames[i];
key = this.entityType.propertiesMap[keyName];
if ((this._getSortedSelectProperties().indexOf(keyName) === -1) &&
(this._SelectedNavigations.indexOf(keyName) === -1)) {
propertyType = key.aggregate ? null : key.DATA_TYPE_NAME;
ret.push(new sqlStatement.SelectProperty(noTable ? null : this._Alias, keyName, propertyType, null));
}
}
return ret;
};
DbSegment.prototype.getKeyPropertiesNotSelectedForCreate = function () {
let ret = [];
let keyNames = this.entityType.keyNamesOrdered;
let propertiesMap = this.entityType.propertiesMap;
for (let i = 0; i < keyNames.length; i++) {
let keyName = keyNames[i];
if ((this._getSortedSelectProperties().indexOf(keyName) === -1) && (this._SelectedNavigations.indexOf(keyName) === -1)) {
let property = propertiesMap[keyName];
let typeString = createPropertyTypeString(property, this.entityType);
ret.push(
new sqlStatement.CreateProperty(keyName, typeString)
);
}
}
return ret;
};
DbSegment.prototype.getPropertiesForCreate = function () {
let ret = [];
//let properties = this.entityType.properties;
let properties = this._getSortedSelectProperties();
for (let i = 0; i < properties.length; i++) {
let property = this.entityType.propertiesMap[properties[i]];
ret.push(
createSqlCreateProperty(property, this.entityType)
);
}
return ret;
};
// Get the properties (as full objects, e.g. with data type etc.) of the associative entity (3rd table)
// via the association's 'over', for CREATE statements
DbSegment.prototype.getOverPropertiesForCreate = function () {
let ret = [];
let over = this.getOver();
let properties = [];
// fetch Key property from original table
const from = this.getFrom();
for (let i = 0; i < from.joinproperties.length; i++) {
const keyPropOrig = this.previousDBSegment.entityType.propertiesMap[from.joinproperties[i]];
const keyProp = xsODataUtils.clone(keyPropOrig);
keyProp.COLUMN_NAME = from.overRefProp[i];// update the column and table name according to the associative table
keyProp.TABLE_NAME = over.object;
keyProp._entityType = this.previousDBSegment.entityType;
properties.push(keyProp);
}
const to = this.getTo();
for (let i = 0; i < to.joinproperties.length; i++) {
const keyPropOrig = this.entityType.propertiesMap[to.joinproperties[i]];
const keyProp = xsODataUtils.clone(keyPropOrig);
keyProp.COLUMN_NAME = to.overRefProp[i];
keyProp.TABLE_NAME = over.object;
keyProp._entityType = this.entityType;
properties.push(keyProp);
}
for (let i = 0; i < properties.length; i++) {
ret.push(
createSqlCreateProperty(properties[i], properties[i]._entityType)
);
}
return ret;
};
function createSqlCreateProperty(property, entityType) {
return new sqlStatement.CreateProperty(property.COLUMN_NAME, createPropertyTypeString(property, entityType));
}
/**
* Create a sql fragment which declares a column
* @param property
* @param entityType
* @returns {string}
*/
function createPropertyTypeString(property, entityType) {
let typeString = property.DATA_TYPE_NAME;
const checkLength = (property) => {
//
if (property.LENGTH === null || property.LENGTH === undefined) {
const name = entityType ? entityType.name : '';
throw new InternalError('Property ' + name + '-' + property.COLUMN_NAME + ' with type ' + property.DATA_TYPE_NAME + ' requires length-setting.');
}
};
if (typeString === 'DECIMAL') {
checkLength(property);
const scale = property.SCALE ? property.SCALE.toString() : 0;
typeString += '(' + property.LENGTH.toString() + ',' + scale + ')';
} else if (typeString === 'VARCHAR') {
checkLength(property);
typeString += '(' + property.LENGTH.toString() + ')';
} else if (typeString === 'NVARCHAR') {
checkLength(property);
typeString += '(' + property.LENGTH.toString() + ')';
} else if (typeString === 'ALPHANUM') {
checkLength(property);
typeString += '(' + property.LENGTH.toString() + ')';
} else if (typeString === 'CHAR') {
checkLength(property);
typeString += '(' + property.LENGTH.toString() + ')';
} else if (typeString === 'NCHAR') {
checkLength(property);
typeString += '(' + property.LENGTH.toString() + ')';
} else if (typeString === 'VARBINARY') {
checkLength(property);
typeString += '(' + property.LENGTH.toString() + ')'; // ON
} else if (typeString === 'SHORTTEXT') {
typeString = 'NVARCHAR(10)';
}
return typeString;
}
/**
* USE CASE accessing calc view with "via key"
* Create sql fragments which select a column either directly with data from DB
* or indirectly with "?" and data from sql parameters ( the later in case of query calculation views)
* @param noTable Provide true if the table should be added to the fragment
* @returns {{selects: Array<SelectProperty>, input: Array<ParameterSelectProperty>}}
*/
DbSegment.prototype.getPropertiesForSelectCollectInputParameters = function (noTable) {
let ret = [];
let retInput = [];
let properties = this._getSortedSelectProperties();
let property;
let selectProperty;
let propertyType;
for (let i = 0; i < properties.length; i++) {
property = this.entityType.propertiesMap[properties[i]];
selectProperty = null;
if (property.KIND === EntityType.entityKind.inputParameters) {
let paramValue = this.getParameterValue(property.COLUMN_NAME);
selectProperty = new sqlStatement.ParameterSelectProperty(noTable ? null : this._Alias, properties[i], null, paramValue);
selectProperty._property = property;
selectProperty._dbType = property.aggregate ? null : property.DATA_TYPE_NAME;
retInput.push(selectProperty);
} else {
propertyType = property.aggregate ? null : property.DATA_TYPE_NAME;
selectProperty = new sqlStatement.SelectProperty(noTable ? null : this._Alias, properties[i], propertyType, null);
ret.push(selectProperty);
}
}
return {
selects: ret,
input: retInput
};
};
/*
* USE CASE accessing calc view without "via key" and normal queries
* Create sql fragments which select a column either directly with data from DB
* or indirectly with "?" and data from sql parameters ( the later in case of query calculation views)
* @param noTable Provide true if the table should be added to the fragment
* @returns Array<SelectProperty|ParameterSelectProperty>}
*/
DbSegment.prototype.getPropertiesForSelect = function (noTable) {
let ret = [],
properties = this._getSortedSelectProperties(),
property,
selectProperty,
propertyType;
for (let i = 0; i < properties.length; i++) {
property = this.entityType.propertiesMap[properties[i]];
selectProperty = null;
if (property.KIND === EntityType.entityKind.inputParameters) {
let paramValue = this.getParameterValue(property.COLUMN_NAME);
selectProperty = new sqlStatement.ParameterSelectProperty(noTable ? null : this._Alias, properties[i], null, paramValue);
} else {
propertyType = property.aggregate ? null : property.DATA_TYPE_NAME;
selectProperty = new sqlStatement.SelectProperty(noTable ? null : this._Alias, properties[i], propertyType, null);
}
ret.push(selectProperty);
}
return ret;
};
/**
* USE CASE accessing calc view with "via key"
* Create sql select fragments to select a all properties (normal and input parameters) from this DbSegment.
* Used in the POST/PUT case to create the select SQL commands for selecting rows from the tmp tables.
* Sample: ... "IN_NVARCHAR" "IN_INTEGER "OUT_DATE"...
* @param noTable Provide true if the table should be added to the fragment
* @returns {Array<SelectProperty>}
*/
DbSegment.prototype.getSelectFragmentsFromAllProperties = function (noTable) {
let ret = [];
let properties = this._getSortedSelectProperties();
for (let i = 0; i < properties.length; i++) {
const property = this.entityType.propertiesMap[properties[i]];
// this include property KIND === inputParameters
const propertyType = property.aggregate ? null : property.DATA_TYPE_NAME;
ret.push(new sqlStatement.SelectProperty(noTable ? null : this._Alias, properties[i], propertyType, null));
}
return ret;
};
/**
* Get the properties (as names only) of the associative entity (3rd table) via the association's 'over', for SELECT statements
* @param rId
* @returns {[]}
*/
DbSegment.prototype.getSelectFragmentsForOverProperties = function (rId) {
let ret = [], selectProperty;
let over = this.getOver();
let properties = [];
for (let i = 0; i < over.principal.length; i++) {
properties.push(over.principal[i]);
}
for (let i = 0; i < over.dependent.length; i++) {
properties.push(over.dependent[i]);
}
for (let i = 0; i < properties.length; i++) {
selectProperty = new sqlStatement.SelectProperty(rId, properties[i], null);
ret.push(
selectProperty
);
}
return ret;
};
DbSegment.prototype.getAllSelectedPropertiesConsideringAggregates = function (noTable) {
let table = noTable ? null : this._Alias;
let withAlias = true;
return this._getSortedSelectProperties().map(createSelectProperty.bind(this, table, withAlias));
};
DbSegment.prototype.getPropertyAsSelectProperty = function (propertyName) {
let withAlias = false;
return createSelectProperty.call(this, this._Alias, withAlias, propertyName);
};
function createSelectProperty(table, withAlias, propertyName) {
/* jshint -W040 */
let aggregate;
let alias;
let property;
let propertyType;
aggregate = this.entityType.getAggregates().filter(function (aggregate) {
return propertyName === aggregate.column;
}).shift();
if (aggregate) {
alias = withAlias && propertyName;
return new sqlStatement.AggregateSelectProperty(aggregate.function.toLowerCase(), table, propertyName, alias);
}
property = this.entityType.propertiesMap[propertyName];
if (property.KIND === EntityType.entityKind.inputParameters) {
return new sqlStatement.ParameterSelectProperty(table, propertyName, null, this.getKeyValue(propertyName));
}
propertyType = property.aggregate ? null : property.DATA_TYPE_NAME;
return new sqlStatement.SelectProperty(table, propertyName, propertyType, null);
/* jshint +W040 */
}
DbSegment.prototype.getNonKeyJoinProperties = function () {
let keys = this.entityType.keyNamesOrdered;
let joinProperties = this.getTo() && this.getTo().joinproperties || [];
let joinProps = joinProperties.filter(function (joinProp) {
return keys.indexOf(joinProp) < 0;
});
return concatJoinProperties([], joinProps, this._Alias);
};
DbSegment.prototype.getAllSelectedIncludingJoinPropertiesConsideringAggregates = function (noTable) {
let table = noTable ? null : this._Alias;
let selectedProperties = this.getAllSelectedPropertiesConsideringAggregates(noTable);
let joinProps = [];
if (this.getTo()) {
joinProps = this.getTo().joinproperties;
} else {
let relevantNavigationSegments = this.getRelevantNavigationSegments();
joinProps = getJoinProperties(relevantNavigationSegments, this._SelectedNavigations);
}
return concatJoinProperties(selectedProperties, joinProps, table);
};
function getJoinProperties(relevantNavigationSegments, selectedNavigations) {
return selectedNavigations.reduce(toJoinProperties, []);
function toJoinProperties(joinProps, selectedNavigation) {
let selectedNavigationSegment = relevantNavigationSegments[selectedNavigation];
if (selectedNavigationSegment) {
let from = selectedNavigationSegment.getFrom();
if (from) {
return joinProps.concat(from.joinproperties);
}
}
return joinProps;
}
}
DbSegment.prototype.getAllSelectedNonAggregatePropertiesForOrderBy = function (noTable) {
let table = noTable ? null : this._Alias;
let entityType = this.entityType;
let selectedProperties = this._getSortedSelectProperties();
if (entityType.hasAggregates()) {
selectedProperties = selectedProperties.filter(aggregates);
}
return selectedProperties.map(toSqlSelectProperty);
function aggregates(selectedProp) {
return !entityType.getAggregates().some(withColumnName);
function withColumnName(aggregate) {
return aggregate.column === selectedProp;
}
}
function toSqlSelectProperty(selectedProperty) {
return new sqlStatement.SelectProperty(table, selectedProperty);
}
};
DbSegment.prototype.getAllSelectedNonAggregateIncludingJoinPropertiesForGroupBy = function (noTable) {
let table = noTable ? null : this._Alias;
let selectedProps = this.getAllSelectedNonAggregatePropertiesForOrderBy(noTable, true);
let joinProps = [];
if (this.getTo()) {
joinProps = this.getTo().joinproperties;
} else {
let relevantNavigationSegments = this.getRelevantNavigationSegments();
joinProps = getJoinProperties(relevantNavigationSegments, this._SelectedNavigations);
}
return concatJoinProperties(selectedProps, joinProps, table);
};
function concatJoinProperties(selectedProps, joinProps, table) {
joinProps.forEach(function (joinProp) {
let shouldAdd = !selectedProps.some(function (selectProp) {
return selectProp.property === joinProp;
});
if (shouldAdd) {
selectedProps.push(new sqlStatement.SelectProperty(table, joinProp, null));
}
});
return selectedProps;
}
DbSegment.prototype.getNavPropertiesForCreate = function () {
let ret = [];
//let properties = this.entityType.properties;
let properties = this._SelectedNavigations;
for (let i = 0; i < properties.length; i++) {
let navPropertyName = properties[i];
let navDbSeg = this._relevantNavigationSegments[navPropertyName];
if (!navDbSeg) {
continue;
}
let propertyNames = navDbSeg.getFrom().joinproperties;
for (let ii = 0; ii < propertyNames.length; ii++) {
let property = this.entityType.propertiesMap[propertyNames[ii]];
let propertyName = property.COLUMN_NAME;
if (this._getSortedSelectProperties().indexOf(propertyName) > -1) {
continue;
}
let typeString = property.DATA_TYPE_NAME;
if (typeString === 'DECIMAL') {
typeString += '(' + property.LENGTH.toString() + ',' + property.SCALE.toString() + ')';
} else if (typeString === 'VARCHAR') {
typeString += '(' + property.LENGTH.toString() + ')';
} else if (typeString === 'NVARCHAR') {
typeString += '(' + property.LENGTH.toString() + ')';
} else if (typeString === 'ALPHANUM') {
typeString += '(' + property.LENGTH.toString() + ')';
} else if (typeString === 'CHAR') {
typeString += '(' + property.LENGTH.toString() + ')';
} else if (typeString === 'NCHAR') {
typeString += '(' + property.LENGTH.toString() + ')';
} else if (typeString === 'VARBINARY') {
typeString += '(' + property.LENGTH.toString() + ')';
} else if (typeString === 'SHORTTEXT') {
typeString = 'NVARCHAR(10)';
}
ret.push(
new sqlStatement.CreateProperty(propertyName, typeString)
);
}
}
return ret;
};
DbSegment.prototype.getNavPropertiesForSelect = function (noTable) {
let ret = [];
//let properties = this.entityType.properties;
let properties = this._SelectedNavigations;
for (let i = 0; i < properties.length; i++) {
let navPropertyName = properties[i];
if (this._getSortedSelectProperties().indexOf(navPropertyName) > -1) {
continue;
}
let navDbSeg = this._relevantNavigationSegments[navPropertyName];
if (!navDbSeg) {
continue;
}
let propertyNames = navDbSeg.getFrom().joinproperties;
for (let ii = 0; ii < propertyNames.length; ii++) {
let property = this.entityType.propertiesMap[propertyNames[ii]];
let propertyName = property.COLUMN_NAME;
if (this._getSortedSelectProperties().indexOf(propertyName) > -1) {
continue;
}
ret.push(
new sqlStatement.SelectProperty(noTable ? null : this._Alias, propertyNames[ii], null)
);
}
}
return ret;
};
/**
* Returns the key names of used/expanded navigation selectProperty list (containing table alias and key name)
* For usage in the select part of sql statements
* @returns {sqlStatement.SelectProperty[]}
*/
DbSegment.prototype.getNonKeyNonSelectedProperties4Expansion = function () {
//
let ret = [];
for (let i = 0; i < this._ExpandedNavigations.length; i++) {
let exp = this._relevantNavigationSegments[this._ExpandedNavigations[i]];
let from = exp._end.from;
for (let ii = 0; ii < from.joinproperties.length; ii++) {
let joinProperty = from.joinproperties[ii];
//Non Key
if (this.entityType.keys.names.indexOf(joinProperty) === -1) {
if (this._getSortedSelectProperties().indexOf(joinProperty) === -1) {
ret.push(
new sqlStatement.SelectProperty(this._Alias, joinProperty, null)
);
}
}
}
}
return ret;
};
/**
* Returns non key properties as array for usage in the INSERT, UPDATE or CREATE TABLE SQL statements.
* @returns {sqlStatement.TableColumn[]}
*/
DbSegment.prototype.getQNonKeyProperties = function () {
let ret = [];
let propertyName;
for (let i = 0; i < this._getSortedSelectProperties().length; i++) {
propertyName = this._getSortedSelectProperties()[i];
if (this.entityType.keys.names.indexOf(propertyName) === -1) {
ret.push(new sqlStatement.TableColumn(this._Alias, propertyName));
}
}
return ret;
};
/**
* Returns non key properties as array for usage in a SELECT SQL statement.
* @returns {sqlStatement.SelectProperty[]}
*/
DbSegment.prototype.getQNonKeyPropertiesForSelect = function () {
let ret = [];
let propertiesMap = this.entityType.propertiesMap;
let propertyName;
let property;
let propertyType;
for (let i = 0; i < this._getSortedSelectProperties().length; i++) {
propertyName = this._getSortedSelectProperties()[i];
if (this.entityType.keys.names.indexOf(propertyName) === -1) {
property = propertiesMap[propertyName];
propertyType = property.aggregate ? null : property.DATA_TYPE_NAME;
ret.push(new sqlStatement.SelectProperty(this._Alias, propertyName, propertyType, null));
}
}
return ret;
};
/**
* for this DBSeg, returns the foreign key for prev/next DBSeg
* */
DbSegment.prototype.getQForeignKeyProperties = function () {
let ret = [];
let fKeys;
if (this.previousDBSegment) {
fKeys = this.getTo().joinproperties;
} else if (this.nextDBSegment) {
let refDBSeg = this.nextDBSegment;
fKeys = refDBSeg.getFrom().joinproperties;
} else {
// error
}
let alias = this._Alias;
fKeys.forEach(function (fkeyName) {
ret.push(
new sqlStatement.SelectProperty(alias, fkeyName, null)
);
});
return ret;
};
/**
* Returns the key names and values
* For usage in where clauses
* @returns {{key : sqlStatement.Property, value : string}[]}
*/
DbSegment.prototype.getQKeyWithValues = function (alias) {
let ret = [];
for (let i = 0; i < this._KeyValues.length; i++) {
let etProperty = this.entityType.propertiesMap[this._KeyValues[i].name];
if (etProperty.KIND !== EntityType.entityKind.inputParameters) {
ret.push({
key: new typedObjects.Property(this._KeyValues[i].name, alias ? alias : this._Alias, etProperty.DATA_TYPE_NAME),
value: this._KeyValues[i].value
});
}
}
return ret;
};
DbSegment.prototype.getQKeyWithValuesDB = function (alias) {
let ret = [];
for (let i = 0; i < this._KeyValues.length; i++) {
ret.push({
key: new typedObjects.Property(this._KeyValues[i].name, alias ? alias : this._Alias, this._KeyValues[i].dbType), //OK
value: new typedObjects.DbValue(this._KeyValues[i].value, this._KeyValues[i].dbType) // Ok
});
}
return ret;
};
DbSegment.prototype.getQForeignKeyWithValues = function (alias) {
let ret = [], refDBSeg, fkeys;
if (this.previousDBSegment) {
refDBSeg = this.previousDBSegment;
fkeys = this.getTo().joinproperties;
} else if (this.nextDBSegment) {
refDBSeg = this.nextDBSegment;
fkeys = refDBSeg.getFrom().joinproperties;
} else {
// error
}
let a = (alias ? alias : this._Alias);
fkeys.forEach(function (fkeyName, i) {
ret.push({
key: new typedObjects.Property(fkeyName, a, refDBSeg._KeyValues[i].dbType), // OK
value: refDBSeg._KeyValues[i].value
});
});
return ret;
};
DbSegment.prototype.getOverPropertiesWithValues = function () {
let ret = [], dbSeg, refDBSeg;
//var a = (alias ? alias : this._Alias);
if (this.previousDBSegment) {
refDBSeg = this.previousDBSegment;
dbSeg = this;
} else if (this.nextDBSegment) {
refDBSeg = this.nextDBSegment;
dbSeg = this;
}
let over = this.getOver();
let keyProp1 = over.principal;
keyProp1.forEach(function (fkeyName, i) {
ret.push({
key: new typedObjects.Property(fkeyName, null, refDBSeg._KeyValues[i].dbType), // OK
value: refDBSeg._KeyValues[i].value
});
});
let keyProp2 = over.dependent;
keyProp2.forEach(function (fKeyName, i) {
ret.push({
key: new typedObjects.Property(fKeyName, null, dbSeg._KeyValues[i].dbType), // OK
value: dbSeg._KeyValues[i].value
});
});
return ret;
};
/*
DbSegment.prototype.dump = function (logger, addText) {
let text = addText || 'DbSegment: ';
let filter = function (key, value) {
if (key === '__metadata' || key === 'previousDBSegment') {
return undefined;
}
return value;
};
logger.debug(text + JSON.stringify(this, filter, 4));
};
*/
/**
* Add a navigationDbSegment to the expand tree
*
* @param navPropName Name of the expanded navigation property
* @param navigationDbSegment Corresponding dbSegment
*/
DbSegment.prototype.addExpandDbSegment = function (navPropName, navigationDbSegment) {
if (this.entityType.navPropertiesMap[navPropName]) {
navigationDbSegment.isExpand = true;
this._ExpandedNavigations.push(navPropName);
this._ExpandedNavigationsDBSeg[navPropName] = navigationDbSegment;
this._relevantNavigationSegments[navPropName] = navigationDbSegment;
}
};
DbSegment.prototype.addRelevantNavigationSegment = function (navPropName, dbSeg) {
if (this.entityType.navPropertiesMap[navPropName]) {
this._relevantNavigationSegments[navPropName] = dbSeg;
}
};
DbSegment.prototype.getRelevantNavigationSegments = fu