@sap/xsodata
Version:
Expose data from a HANA database as OData V2 service with help of .xsodata files.
1,568 lines (1,389 loc) • 60.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;
const isSpecialCharacter = (
createBy,
createAt,
modifyBy,
modifyAt,
property
) => {
return (
(createBy && property === 'CREATED_BY') ||
(createAt && property === 'CREATED_AT') ||
(modifyBy && property === 'MODIFIED_BY') ||
(modifyAt && property === 'MODIFIED_AT')
);
};
/**
* 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 (const keyValue of this._KeyValues) {
if (keyValue.name === key) {
return keyValue;
}
}
return undefined;
};
DbSegment.prototype.hasKeyValues = function () {
return this._KeyValues.length > 0;
};
DbSegment.prototype._isParamViaKey = function () {
return (
this.entityType._entityType.parameters &&
this.entityType._entityType.parameters.viaKey === true
);
};
DbSegment.prototype._getDBValue = function (
value,
key,
prop,
context,
supportNullable,
converterArray,
entityTypes
) {
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) {
const createBy = entityTypes[0],
createAt = entityTypes[1],
modifyBy = entityTypes[2],
modifyAt = entityTypes[3];
if (
!isSpecialCharacter(createBy, createAt, modifyBy, modifyAt, key)
) {
throw new BadRequest(
'The serialized resource has a missing value for member ' +
key
);
}
}
dbValue = null;
} else {
if (this._isParamViaKey()) {
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);
}
return dbValue;
};
/**
* 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 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.
for (let [key, prop] of Object.entries(this.entityType.propertiesMap)) {
//Load property from record
const value = record[key];
let dbValue = this._getDBValue(
value,
key,
prop,
context,
supportNullable,
converterArray,
[createBy, createAt, modifyBy, modifyAt]
);
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
(
isSpecialCharacter(
createBy,
createAt,
modifyBy,
modifyAt,
property
) ||
// 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 (const element of this.entityType.keys.names) {
ret.push(new sqlStatement.TableColumn(this._Alias, element));
}
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 (const element of this._InputParams) {
let keyName = element.key;
if (!this.entityType.inputParameters[keyName]) {
throw new InternalError('Inputparameter ' + keyName + ' unknown');
}
}
};
DbSegment.prototype.validateKeyParameters = function () {
const keyNames = this.entityType.keyNamesOrdered;
for (const element of keyNames) {
const property = this.entityType.propertiesMap[element];
if (property.KIND !== EntityType.entityKind.inputParameters) {
if (!this._InputParamsMap.has(element)) {
throw new InternalError(
'Missing value for input parameter ' + element + '.'
);
}
}
}
};
/**
* 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 (const prop of this._aliasedKeyPropertiesOnCalcView) {
if (prop.property === propertyName) {
return prop;
}
}
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 property;
let j = -1;
let keyNames = this.entityType.keyNamesOrdered;
for (const keyName of keyNames) {
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 (const keyName of keyNames) {
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._getSortedSelectProperties();
for (const prop of properties) {
let property = this.entityType.propertiesMap[prop];
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 (const property of properties) {
ret.push(createSqlCreateProperty(property, property._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' ||
typeString === 'NVARCHAR' ||
typeString === 'ALPHANUM' ||
typeString === 'CHAR' ||
typeString === 'NCHAR' ||
typeString === 'VARBINARY'
) {
checkLength(property);
typeString += '(' + property.LENGTH.toString() + ')';
} 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 (const prop of properties) {
property = this.entityType.propertiesMap[prop];
selectProperty = null;
if (property.KIND === EntityType.entityKind.inputParameters) {
let paramValue = this.getParameterValue(property.COLUMN_NAME);
selectProperty = new sqlStatement.ParameterSelectProperty(
noTable ? null : this._Alias,
prop,
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,
prop,
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 (const prop of properties) {
property = this.entityType.propertiesMap[prop];
selectProperty = null;
if (property.KIND === EntityType.entityKind.inputParameters) {
let paramValue = this.getParameterValue(property.COLUMN_NAME);
selectProperty = new sqlStatement.ParameterSelectProperty(
noTable ? null : this._Alias,
prop,
null,
paramValue
);
} else {
propertyType = property.aggregate ? null : property.DATA_TYPE_NAME;
selectProperty = new sqlStatement.SelectProperty(
noTable ? null : this._Alias,
prop,
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 (const prop of properties) {
const property = this.entityType.propertiesMap[prop];
// this include property KIND === inputParameters
const propertyType = property.aggregate
? null
: property.DATA_TYPE_NAME;
ret.push(
new sqlStatement.SelectProperty(
noTable ? null : this._Alias,
prop,
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 (const property of over.principal) {
properties.push(property);
}
for (const property of over.dependent) {
properties.push(property);
}
for (const property of properties) {
selectProperty = new sqlStatement.SelectProperty(rId, property, 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._SelectedNavigations;
for (const navPropertyName of properties) {
let navDbSeg = this._relevantNavigationSegments[navPropertyName];
if (!navDbSeg) {
continue;
}
let propertyNames = navDbSeg.getFrom().joinproperties;
for (const prop of propertyNames) {
let property = this.entityType.propertiesMap[prop];
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._SelectedNavigations;
for (const navPropertyName of properties) {
if (this._getSortedSelectProperties().indexOf(navPropertyName) > -1) {
continue;
}
let navDbSeg = this._relevantNavigationSegments[navPropertyName];
if (!navDbSeg) {
continue;
}
let propertyNames = navDbSeg.getFrom().joinproperties;
for (const prop of propertyNames) {
let property = this.entityType.propertiesMap[prop];
let propertyName = property.COLUMN_NAME;
if (this._getSortedSelectProperties().indexOf(propertyName) > -1) {
continue;
}
ret.push(
new sqlStatement.SelectProperty(
noTable ? null : this._Alias,
prop,
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 (const expandedNavigation of this._ExpandedNavigations) {
let exp = this._relevantNavigationSegments[expandedNavigation];
let from = exp._end.from;
for (const joinProperty of from.joinproperties) {
//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 = [];
for (const propertyName of this._getSortedSelectProperties()) {
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 property;
let propertyType;
for (const propertyName of this._getSortedSelectProperties()) {
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 (const keyValue of this._KeyValues) {
let etProperty = this.entityType.propertiesMap[keyValue.name];
if (etProperty.KIND !== EntityType.entityKind.inputParameters) {
ret.push({
key: new typedObjects.Property(
keyValue.name,
alias ? alias : this._Alias,
etProperty.DATA_TYPE_NAME
),
value: keyValue.value,
});
}
}
return ret;
};
DbSegment.prototype.getQKeyWithValuesDB = function (alias) {
let ret = [];
for (const keyValue of this._KeyValues) {
ret.push({
key: new typedObjects.Property(
keyValue.name,
alias ? alias : this._Alias,
keyValue.dbType
), //OK
value: new typ