UNPKG

@sap/xsodata

Version:

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

823 lines (674 loc) 34.1 kB
'use strict'; const sql = require('./sqlStatement'); const edm = require('./../utils/typedObjects'); const sqlTools = require('./sqlTools'); const EntityType = require('../model/entityType.js'); const addETagToSelect = sql._addETagToSelect; const utils = require('../utils/utils'); const BadRequest = require('../utils/errors/http/badRequest'); exports._masterTableSimpleSelect = masterTableSimpleSelect; exports._expandSimpleSelect = expandSimpleSelect; exports._expandInsert = expandInsert; exports._masterTableInsert = masterTableInsert; //Code /** * Create all SQL statements required for GET requests * The SQL statements are stored directly in context.sql and dbSegments.sql.stmContainer. * @param context * @param asyncDone * @returns {*} */ exports.createGetSqlStatements = function (context, asyncDone) { var sqlContext = context.sql = { netId: context.uniqueNetworkRequestID, reqId: context.uniqueRequestID, rId: 1, dbSegLast: context.oData.dbSegmentLast, systemQueryParameters: context.oData.systemQueryParameters, context: context }; context.logger.silly('createGetStatements', 'createGetSqlStatements'); sqlContext.dbSegLast.sql.stmContainer = new sql.GetContainer(); sqlContext.rId = 1; context.sql.cleanSessionTruncateContainer = []; context.sql.cleanSessionDropContainer = []; masterTable(sqlContext); expandedTables(sqlContext, sqlContext.dbSegLast, '', []); context.sql.container = sqlTools.collectSqls(context, sqlContext); return asyncDone(null, context); }; /** * Create SQL statements for the master table (=end of resource path) * @param sqlContext */ function masterTable(sqlContext) { let dbSeg = sqlContext.dbSegLast; sqlContext.context.logger.debug('createGetStatements', 'masterTable'); //dbSeg.sql.stmContainer = new sql.GetContainer(); if (dbSeg.restriction.onlyCount === true) { //no expand, avoid temporary tables dbSeg.sql.stmContainer.select = exports._masterTableSimpleSelect(sqlContext); dbSeg.sql.stmContainer.select.setAddCalcViewHint(); dbSeg.sql.stmContainer.select.setHints(sqlContext.context.gModel.getHints()); } else if (dbSeg.restriction.onlyValue === true) { //no expand, avoid temporary tables dbSeg.sql.stmContainer.select = exports._masterTableSimpleSelect(sqlContext); dbSeg.sql.stmContainer.select.setAddCalcViewHint(); dbSeg.sql.stmContainer.select.setHints(sqlContext.context.gModel.getHints()); } else if (dbSeg._ExpandedNavigations.length === 0) { //no expand, avoid temporary tables dbSeg.sql.stmContainer.select = exports._masterTableSimpleSelect(sqlContext); dbSeg.sql.stmContainer.select.setAddCalcViewHint(); dbSeg.sql.stmContainer.select.setHints(sqlContext.context.gModel.getHints()); } else { dbSeg.sql.rId = sqlTools.rIdToTableName(sqlContext.reqId, sqlContext.rId++); //create statement for master table dbSeg.sql.stmContainer.createTmp = masterTableCreate(sqlContext, dbSeg.sql.rId); //create insert statement for master table dbSeg.sql.stmContainer.insertTmp = masterTableInsert(sqlContext); //create select statement for master table dbSeg.sql.stmContainer.selectTmp = masterTableSelect(sqlContext); dbSeg.sql.stmContainer.selectTmp.setAddCalcViewHint(); dbSeg.sql.stmContainer.selectTmp.setHints(sqlContext.context.gModel.getHints()); if (sqlContext.context.db && sqlContext.context.db.isExternalHandledConnection === true) { // We only truncate and delete temp tables when db connection is handled external // otherwise we will self disconnect and temp tables will be deleted automatically // build the truncate statement for created temporary table; const truncateStmt = sql.buildTableStatement(sql.Truncate, sqlContext, dbSeg.sql.stmContainer.createTmp.table); const truncateContainer = sqlContext.context.sql.cleanSessionTruncateContainer; truncateContainer.push(truncateStmt); sqlContext.context.networkContext.cleanSessionTruncateContainer.push(truncateStmt); // build the drop statement for created temporary table; const dropStmt = sql.buildTableStatement(sql.Drop, sqlContext, dbSeg.sql.stmContainer.createTmp.table); const dropContainer = sqlContext.context.sql.cleanSessionDropContainer; dropContainer.push(dropStmt); sqlContext.context.networkContext.cleanSessionDropContainer.push(dropStmt); } } } /** * Load the limit information from the xsodata file * @param sqlContext * @param {string} name Name of the limit setting (e.g. 'max_records') * @returns {*} */ function getLimit(sqlContext, name) { if (!sqlContext) {return null;} const context = sqlContext.context; if (!context) {return null;} const model = context.gModel; if (model && model._settings && model._settings.limits && model._settings.limits[name]) { const limit = model._settings.limits[name]; return Number.parseInt(limit); } else { if (name === 'max_expanded_records') { return 10000; } else { return 1000; } } return null; } function masterTableSimpleSelect(sqlContext, countFallback) { // SAPINFO A simple odata GET request without and expand is processed without using temporary tables. // Count fallback it true if a SELECT is created which is used to obtain the max count for all records, even // if the result set is empty. (If the result is not empty there would be a count column) var dbSeg, isCount, select, orderBy, selectCount; sqlContext.context.logger.debug('createGetStatements', 'masterTableSimpleSelect'); dbSeg = sqlContext.dbSegLast; isCount = dbSeg.restriction.onlyCount || countFallback; //create select statement select = simpleSelectJoins(dbSeg, sqlContext); //add Query $filter if (sqlContext.systemQueryParameters.filter) { if (!checkConditionsProps(sqlContext.systemQueryParameters.filter, dbSeg.entityType.propertiesMap)) { throw new BadRequest('Unknown filter property'); } // Add WHERE statement from uri if (sqlContext.systemQueryParameters.filter.setAlias) { //filters like '$filter=1' have no alias because there are just literals sqlContext.systemQueryParameters.filter.setAlias(dbSeg.getAlias()); } select.addWhereAnded(sqlContext.systemQueryParameters.filter); } //add Query $orderby if (!isCount) { if (sqlContext.systemQueryParameters.orderby) { if (!checkOrderbyProps(sqlContext.systemQueryParameters.orderby, dbSeg.entityType.propertiesMap)) { throw new BadRequest('Unknown orderby property'); } orderBy = sqlContext.systemQueryParameters.orderby; if (orderBy.setAlias) { // Add WHERE statement from uri orderBy.setAlias(dbSeg.getAlias()); } if (orderBy.applyConverter) { orderBy.applyConverter(replaceEdmPropertiesWithSqlProperties.bind(null, dbSeg)); } select.addOrderBy(orderBy); } } // group by & order by if (dbSeg.entityType.hasAggregates()) { select.addGroupBys(dbSeg.getAllSelectedNonAggregateIncludingJoinPropertiesForGroupBy()); if (!isCount) { select.addSortOrders(dbSeg.getAllSelectedNonAggregatePropertiesForOrderBy(null, true)); } } else { if (!isCount) { if (!(dbSeg.entityType.kind === EntityType.entityKind.calculationView && dbSeg.entityType._entityType.parameters && dbSeg.entityType._entityType.parameters.viaKey === true)) { select.addSortOrders(dbSeg.getKeyProperties0123ForOrderBy(true)); } else { let orderByProperties = dbSeg.getKeyProperties0123ForOrderByCalcView(true); for (let i= 0; i < select.select.length; i++) { for (let j= 0; j < orderByProperties.length; j++) { if (orderByProperties[j].getPropertyName() === select.select[i].property) { select.select[i].alias = orderByProperties[j].expression.property; } } } select.addSortOrders(orderByProperties); } } } //add $skip --> Offset if (sqlContext.systemQueryParameters.skip && !countFallback) { select.addOffset(sqlContext.systemQueryParameters.skip); } //add $top --> Limit if (!isCount) { const maxRecords = getLimit(sqlContext, 'max_records'); const top = sqlContext.systemQueryParameters.top; if (maxRecords) { select.setCustomData('maxRecords', maxRecords); if (top >= 0) { if (top > maxRecords) { // Value of $top is ok. If there are only max_records records these are returned. // If there are max_records+1 or more records an error will be thrown select.addLimit(maxRecords + 1); } else { select.addLimit(top); } } else { // Add a max_records+1 limit. If there are only max_records records these are returned. // If there are max_records+1 or more records an error will be thrown select.addLimit(maxRecords + 1); } } else { if (top >= 0) { select.addLimit(top); } } } if (!countFallback) { if (sqlContext.systemQueryParameters.inlinecount) { select.addFallbackStatement(masterTableSimpleSelect(sqlContext, true)); } } if (isCount) { //wrap select selectCount = new sql.Select(); selectCount.setFromBySubQuery(select); selectCount.addSelects([new sql.SelectFormula(null, 'count(*)', 'c')]); } else if (!sqlContext.systemQueryParameters.expand && !countFallback && utils.isETagRequired(sqlContext.context, dbSeg)) { addETagToSelect(dbSeg, select); } return selectCount || select; /** * scan the tree of conditions, and check that every given property exist in the corresponding entityType * @param {object} tree - the conditions tree. It consists of an object with further objects, arrays or properties * @param {object} props - the propertiesMap of the given entityType * @return {boolean} * */ function checkConditionsProps(tree, props) { var objpr; // loop over all properties for (objpr in tree) { if (tree.hasOwnProperty(objpr)) { // if the property is an object itself, then recursively call the check if (typeof tree[objpr] === 'object') { if (!checkConditionsProps(tree[objpr], props)) { return false; } } else if (objpr === 'property' && !props.hasOwnProperty(tree[objpr])) { return false; } } } return true; } /** * scan the array of $orderby orders, and check that every given property exist in the corresponding entityType * @param {object} tree - the conditions tree. It consists of an object with the array of orders * @param {object} props - the propertiesMap of the given entityType * @return {boolean} * */ function checkOrderbyProps(tree, props) { var expression, i; // loop over all orders for (i = 0; i < tree.orders.length; i++) { expression = tree.orders[i].expression; // loop over all properties if (!checkConditionsProps(expression, props)) { return false; } } return true; } /** * handle the MxN properties in the associative 'over' table * */ function handleKeyPropertiesInOverTable(inner, dbSeg) { var seg1overProp, seg1parentsJoinProp, seg1childsJoinProp; var seg2overProp, seg2parentsJoinProp, seg2childsJoinProp; var i; //create join property list from the first end ('dependent' | 'principal') seg1overProp = (dbSeg.isPrincipal() ? 'dependent' : 'principal'); // seg1 is on the contrary to dbSeg (that referes to dbSegmentLast seg1parentsJoinProp = dbSeg.getFrom().joinproperties; seg1childsJoinProp = dbSeg.getOver()[seg1overProp]; for (i = 0; i < seg1parentsJoinProp.length; i++) { inner.addWhereAnded(new edm.BinaryOperator(edm.EQ, /* no dbType required as the binary operator compares to properties, not property and value */ new edm.Property(seg1parentsJoinProp[i], dbSeg.previousDBSegment.getAlias()), /* no dbType required as the binary operator compares to properties, not property and value */ new edm.Property(seg1childsJoinProp[i], dbSeg.previousDBSegment.getAssocAlias()), true /*no OData like null handling*/ )); } //create join property list from the second end seg2overProp = (dbSeg.isPrincipal() ? 'principal' : 'dependent'); seg2parentsJoinProp = dbSeg.getOver()[seg2overProp]; seg2childsJoinProp = dbSeg.getTo().joinproperties; for (i = 0; i < seg2parentsJoinProp.length; i++) { inner.addWhereAnded(new edm.BinaryOperator(edm.EQ, /* no dbType required as the binary operator compares to properties, not property and value */ new edm.Property(seg2childsJoinProp[i], dbSeg.getAlias()), /* no dbType required as the binary operator compares to properties, not property and value */ new edm.Property(seg2parentsJoinProp[i], dbSeg.previousDBSegment.getAssocAlias()), true /*no OData like null handling*/ )); } } /** * handle the 1xN properties in the corresponding table * */ function handleKeyProperties(inner, dbSeg) { //create join property list const parentsJoinProp = dbSeg.getFrom().joinproperties; const childsJoinProp = dbSeg.getTo().joinproperties; var i; for (i = 0; i < parentsJoinProp.length; i++) { inner.addWhereAnded(new edm.BinaryOperator(edm.EQ, /* no dbType required as the binary operator compares to properties, not property and value */ new edm.Property(parentsJoinProp[i], dbSeg.previousDBSegment.getAlias()), /* no dbType required as the binary operator compares to properties, not property and value */ new edm.Property(childsJoinProp[i], dbSeg.getAlias()), true /*no OData like null handling*/ )); } } /** * Creates a sql command to select data to which the resource path points to, all segments of the resource path with their dbSeg equivalents are joined * Sample: select "FromEntity1"."KEY_1" "0", "FromEntity1"."KEY_1", "FromEntity1"."INTEGER_1", "FromEntity1"."NVARCHAR_1" from "xsodata.test.tables::calc_nav__entity_from" as "FromEntity1" * @param dbSeg * @param sqlContext * @returns {Select} */ function simpleSelectJoins(dbSeg, sqlContext) { sqlContext.context.logger.debug('createGetStatements', 'simpleSelectJoins'); //create select statement var stm = new sql.Select(); var inner = null; if (dbSeg.previousDBSegment) { //end of recursion inner = simpleSelectJoins(dbSeg.previousDBSegment, sqlContext); if (dbSeg.getOver()) { handleKeyPropertiesInOverTable(inner, dbSeg); } else { handleKeyProperties(inner, dbSeg); } } //SELECT if (dbSeg.nextDBSegment) { stm.addSelects([new sql.SelectFormula(null, '1')]); } else { if (dbSeg.entityType.hasGeneratedKey()) { stm.addSelects(dbSeg.getAllSelectedPropertiesConsideringAggregates()); } else { if (dbSeg.entityType.kind === EntityType.entityKind.calculationView && dbSeg.entityType._entityType.parameters && dbSeg.entityType._entityType.parameters.viaKey === true) { // Adding properties from getKeyProperties0123ForSelectAs0123 is not needed since // if (in future) an association via input parameter is used then the input parameter values must be added via ? into the sql command // that's because calcview don't return their input parameter in the result set let tmp = dbSeg.getPropertiesForSelectCollectInputParameters(); // since the input parameters may not be returned from the calcview the parameters are merged after the select // from "calcview2" into the result. // Q: What does that mean: "calcview2"? stm.byPassInputs = tmp.input; stm.addSelects(tmp.selects); stm.addSelects(dbSeg.getKeyPropertiesNotSelectedForSelect(undefined, dbSeg.entityType.inputParameters)); // keep aliased key properties let selectProperties = dbSeg.getKeyProperties0123ForSelectOnCalcViewAs0123(); dbSeg.setAliasedKeyPropertiesOnCalcView(selectProperties); } else { stm.addSelect(dbSeg.getKeyProperties0123ForSelectAs0123()); stm.addSelects(dbSeg.getPropertiesForSelect()); stm.addSelects(dbSeg.getKeyPropertiesNotSelectedForSelect()); } //} //stm.addSelects(dbSeg.getAllSelectedPropertiesConsideringAggregates()); } if (dbSeg._ExpandedNavigations.length > 0 || dbSeg._SelectedNavigations.length > 0) { stm.addSelects(dbSeg.getNavPropertiesForSelect()); } //stm.addSelects(dbSeg.getQNonKeyProperties()); if (sqlContext.systemQueryParameters.inlinecount && !countFallback) { stm.addSelects(new sql.SelectFormula(null, 'count(*) over () as ', '0count')); } } //FROM if (dbSeg.nextDBSegment && dbSeg.nextDBSegment.getOver()) { stm.addFrom({ schema: dbSeg._DB_Schema, table: dbSeg.nextDBSegment.getOver().object, alias: dbSeg.getAssocAlias() }); } var fromSource = dbSeg.getAliasedTableName(); //fromSource.parameters = dbSeg.inputParams; stm.addFrom(fromSource, dbSeg.getInputParameters()); if (inner) { //WHERE set Exists first stm.addWhereAnded(new sql.Exists(inner)); } //WHERE set Keys next stm.addWhereKeyValuePairs(dbSeg.getQKeyWithValues()); return stm; } } function replaceEdmPropertiesWithSqlProperties(dbSeg, expression) { if (expression instanceof edm.Property) { return new edm.Parenthesis(dbSeg.getPropertyAsSelectProperty(expression.property)); } return expression; } function masterTableCreate(sqlContext, rId) { sqlContext.context.logger.debug('createGetStatements', 'masterTableCreate'); var dbSeg = sqlContext.dbSegLast; var isCount = sqlContext.systemQueryParameters.inlinecount; var stmCreate = new sql.Create(); stmCreate.setModifiers(['local', 'temporary', dbSeg.entityType.tableStoreType]); stmCreate.setTableName(rId); if (dbSeg.entityType.hasGeneratedKey()) { stmCreate.addProperties(dbSeg.getPropertiesForCreate()); } else { stmCreate.addProperties(dbSeg.getKeyProperties0123ForCreate()); stmCreate.addProperties(dbSeg.getPropertiesForCreate()); stmCreate.addProperties(dbSeg.getKeyPropertiesNotSelectedForCreate()); // SKDO for uri building } if (dbSeg._ExpandedNavigations.length > 0 || dbSeg._SelectedNavigations.length > 0) { stmCreate.addProperties(dbSeg.getNavPropertiesForCreate()); } if (isCount) { stmCreate.addProperty(new sql.CreateProperty('0count', 'BIGINT')); } stmCreate.addProperty(new sql.CreateProperty('1row', 'BIGINT')); return stmCreate; } function masterTableInsert(sqlContext) { sqlContext.context.logger.debug('createGetStatements', 'masterTableInsert'); //SystemQuery option order: $format, $inlinecount, $filter, $orderby, $skiptoken, $skip, $top, $expand. var dbSeg = sqlContext.dbSegLast; var stmInsert = new sql.Insert(); stmInsert.setTableName({ table: dbSeg.sql.rId }); var selectRow = new sql.Select(); selectRow.addSelects(new sql.SelectFormula(null, '*')); selectRow.addSelects(new sql.OverFormula('1row')); var subSelect = exports._masterTableSimpleSelect(sqlContext); selectRow.setFromBySubQuery(subSelect); stmInsert.setSubSelect(selectRow); return stmInsert; } function masterTableSelect(sqlContext) { sqlContext.context.logger.debug('createGetStatements', 'masterTableSelect'); var dbSeg = sqlContext.dbSegLast; var stmSelect = new sql.Select(); var isCount = sqlContext.systemQueryParameters.inlinecount; addSelectsForSelectFromTmp(stmSelect, dbSeg); stmSelect.addSelects([new sql.SelectProperty(null, '1row')]); if (isCount) { stmSelect.addSelects([new sql.SelectProperty(null, '0count')]); } else if (utils.isETagRequired(sqlContext.context, dbSeg)) { addETagToSelect(dbSeg, stmSelect, dbSeg.sql.rId); } // If at least one expand is used, then a tmp table is used to collect the // master tables records first. Into this tmp table only max_records entries // are inserted (because a limit is added to the inserts subquery, e.g. // insert into "#r0_1" ( // select *, row_number() over() "1row" from ( // select "a_key1"."KEY" "0", "a_key1"."KEY", "a_key1"."INFO", "a_key1"."NOKEY", count(*) over () as "0count" // from "xsodata.test.tables::massdata_a_key" as "a_key1" // order by "0" limit ?)) <--- added limit // ). Since there is no direct select execution of the 2nd inner select, because the tmp table is read with a // direct select on the tmp table, the maxRecords must be added to this direct select. const maxRecords = getLimit(sqlContext, 'max_records'); stmSelect.setCustomData('maxRecords', maxRecords); addFromAndSortOrderForSelectFromTmp(stmSelect, dbSeg.sql.rId); return stmSelect; } function expandedTables(sqlContext, parentSeg, path) { var navigationNames = parentSeg._ExpandedNavigations, truncateStmt, dropStmt, truncateContainer = sqlContext.context.sql.cleanSessionTruncateContainer, dropContainer = sqlContext.context.sql.cleanSessionDropContainer; //loop thought expanded tables for (var i = 0; i < navigationNames.length; i++) { var dbSegExpanded = parentSeg.getRelevantNavigationSegments()[navigationNames[i]]; dbSegExpanded.sql.stmContainer = new sql.GetContainer(); //check if it makes sense to create a tmp table (this is done only if there are further //expands on the already expanded table. if (dbSegExpanded._ExpandedNavigations.length > 0) { dbSegExpanded.sql.rId = sqlTools.rIdToTableName(sqlContext.reqId, sqlContext.rId++); dbSegExpanded.sql.path = path + '/' + dbSegExpanded.getAlias(); //Sql for creating tmp table dbSegExpanded.sql.stmContainer.createTmp = expandCreate(dbSegExpanded); //Sql for filling tmp table dbSegExpanded.sql.stmContainer.insertTmp = exports._expandInsert(dbSegExpanded, sqlContext); //Sql for reading tmp table dbSegExpanded.sql.stmContainer.selectTmp = expandSelect(dbSegExpanded, sqlContext); if (sqlContext.context.db && sqlContext.context.db.isExternalHandledConnection === true) { // We only truncate and delete temp tables when db connection is handled external // otherwise we will self disconnect and temp tables will be deleted automatically // build the truncate statement for created temporary table; truncateStmt = sql.buildTableStatement(sql.Truncate, sqlContext, dbSegExpanded.sql.stmContainer.createTmp.table); truncateContainer.push(truncateStmt); sqlContext.context.networkContext.cleanSessionTruncateContainer.push(truncateStmt); // build the drop statement for created temporary table; dropStmt = sql.buildTableStatement(sql.Drop, sqlContext, dbSegExpanded.sql.stmContainer.createTmp.table); dropContainer.push(dropStmt); sqlContext.context.networkContext.cleanSessionDropContainer.push(dropStmt); } } else { //no, makes not sense, just do a normal select dbSegExpanded.sql.stmContainer.select = exports._expandSimpleSelect(dbSegExpanded, true, sqlContext); } //recurse expandedTables(sqlContext, dbSegExpanded, path + '/' + dbSegExpanded.getAlias()); } } function expandCreate(dbSeg) { var stmCreate = new sql.Create(); stmCreate.setModifiers(['local', 'temporary', dbSeg.entityType.tableStoreType]); stmCreate.setTableName(dbSeg.sql.rId); stmCreate.addProperty(new sql.CreateProperty('0row', 'BIGINT')); stmCreate.addProperty(new sql.CreateProperty('1row', 'BIGINT')); if (dbSeg.entityType.hasGeneratedKey()) { stmCreate.addProperties(dbSeg.getPropertiesForCreate()); } else { stmCreate.addProperties(dbSeg.getKeyProperties0123ForCreate()); stmCreate.addProperties(dbSeg.getPropertiesForCreate()); stmCreate.addProperties(dbSeg.getKeyPropertiesNotSelectedForCreate()); } if (dbSeg._ExpandedNavigations.length > 0 || dbSeg._SelectedNavigations.length > 0) { stmCreate.addUniqueProperties(dbSeg.getNavPropertiesForCreate()); } return stmCreate; } function expandInsert(dbSeg, sqlContext) { var subSelect = exports._expandSimpleSelect(dbSeg, undefined, sqlContext); var stmInsert = new sql.Insert(); stmInsert.setTableName({ table: dbSeg.sql.rId }); stmInsert.setSubSelect(subSelect); return stmInsert; } /** * Creates SELECT statement for the entities, which should be expanded. * @param {object} dbSeg - DBSegment representing the entity type of the entities, which should be expanded * @param {boolean} [addEtag] - flag indicating whether ETag generation expression should be added to the created * SELECT statement * @param {object} [sqlContext] - SQL context * @returns {Select} */ function expandSimpleSelect(dbSeg, addEtag, sqlContext) { var subSelect = new sql.Select(); //SELECT //this adds the 1row index from the parent table. subSelect.addSelects(new sql.SelectProperty(dbSeg.previousDBSegment.getAlias(), '1row', null, '0row')); var over = createOverWithOrderBys(dbSeg); //this ensure the subSelect.addSelects(over); //Over with 1row is not required here because this dbSeg has no expands subSelect.addSelect(new sql.SelectFormula(dbSeg._Alias, '*', null)); if (addEtag && utils.isETagRequired(sqlContext.context, dbSeg)) { addETagToSelect(dbSeg, subSelect); } //FROM subSelect.setFrom({ schema: null, table: dbSeg.previousDBSegment.sql.rId, alias: dbSeg.previousDBSegment.getAlias() }); var onStatements = createOnStatements(dbSeg); if (onStatements.first.length > 0) { subSelect.addJoin({ table: dbSeg.getOver().object, alias: dbSeg.getAssocAlias() }, onStatements.first); } var joinSelect = new sql.Select(); if (dbSeg.entityType.hasGeneratedKey()) { joinSelect.addSelects(dbSeg.getAllSelectedIncludingJoinPropertiesConsideringAggregates()); } else { joinSelect.addSelects(dbSeg.getQKeyPropertiesWith0123AliasForSelect()); joinSelect.addSelects(dbSeg.getPropertiesForSelect()); joinSelect.addSelects(dbSeg.getKeyPropertiesNotSelectedForSelect()); joinSelect.addUniqueSelects(dbSeg.getNonKeyJoinProperties()); } if (dbSeg._ExpandedNavigations.length > 0 || dbSeg._SelectedNavigations.length > 0) { joinSelect.addUniqueSelects(dbSeg.getNavPropertiesForSelect()); } joinSelect.setFrom(dbSeg.getAliasedTableName()); // group bys if (dbSeg.entityType.hasAggregates()) { var groupBys = dbSeg.getAllSelectedNonAggregateIncludingJoinPropertiesForGroupBy(); joinSelect.addGroupBys(groupBys); } subSelect.addJoinTable(joinSelect, onStatements.second, dbSeg._Alias); /* no dbType required as property is used only for SortOrder */ subSelect.addSortOrder(new edm.SortOrder(new edm.Property('1row'), 'ASC')); const maxExpandedRecords = getLimit(sqlContext, 'max_expanded_records'); if (maxExpandedRecords) { subSelect.setCustomData('maxExpandedRecords', maxExpandedRecords); // Add a maxExpandedRecords+1 limit. If there are only maxExpandedRecords records these are returned. // If there are maxExpandedRecords+1 or more records an error will be thrown // With OData V2 its not possible to restrict the number of expanded records subSelect.addLimit(maxExpandedRecords + 1); } return subSelect; } function createOverWithOrderBys(dbSeg) { var over = new sql.OverFormula('1row'); //this ensure the /* no dbType required as property is used only for SortOrder */ over.addSortOrder(new edm.SortOrder(new edm.Property('1row', dbSeg.previousDBSegment.getAlias()), 'ASC')); if (dbSeg.entityType.hasGeneratedKey()) { over.addSortOrders(dbSeg.getAllSelectedNonAggregatePropertiesForOrderBy(null, true)); } else { over.addSortOrders(dbSeg.getKeyProperties0123ForOrderBy()); } return over; } function createOnStatements(dbSeg) { var parentsJoinProps = dbSeg.getFrom().joinproperties; var childJoinProps = dbSeg.getTo().joinproperties; var overJoinProps = dbSeg.getOver(); var onStatements = { first: [], second: [] }; if (overJoinProps) { return parentsJoinProps.reduce(createOnStatementWithOver, onStatements); } else { return parentsJoinProps.reduce(createOnStatementWithoutOver, onStatements); } function createOnStatementWithOver(onStatements, parentsJoinProp, index) { onStatements.first.push(createOnStatement( parentsJoinProp, dbSeg.previousDBSegment.getAlias(), overJoinProps.principal[index], dbSeg.getAssocAlias() )); onStatements.second.push(createOnStatement( childJoinProps[index], dbSeg.getAlias(), overJoinProps.dependent[index], dbSeg.getAssocAlias() )); return onStatements; } function createOnStatementWithoutOver(onStatements, parentsJoinProp, index) { onStatements.second.push(createOnStatement( parentsJoinProp, dbSeg.previousDBSegment.getAlias(), childJoinProps[index], dbSeg.getAlias() )); return onStatements; } function createOnStatement(firstProp, firstAlias, secondProp, secondAlias) { return new edm.BinaryOperator(edm.EQ, /* no dbType required as the binary operator compares to properties, not property and value */ new edm.Property(firstProp, firstAlias), /* no dbType required as the binary operator compares to properties, not property and value */ new edm.Property(secondProp, secondAlias), true /*no OData like null handling*/ ); } } function expandSelect(dbSeg, sqlContext) { var subSelect = new sql.Select(); subSelect.addSelects([new sql.SelectProperty(null, '0row')]); subSelect.addSelects([new sql.SelectProperty(null, '1row')]); addSelectsForSelectFromTmp(subSelect, dbSeg); if (utils.isETagRequired(sqlContext.context, dbSeg)) { addETagToSelect(dbSeg, subSelect, dbSeg.sql.rId); } addFromAndSortOrderForSelectFromTmp(subSelect, dbSeg.sql.rId); const maxExpandedRecords = getLimit(sqlContext, 'max_expanded_records'); if (maxExpandedRecords) { subSelect.setCustomData('maxExpandedRecords', maxExpandedRecords); // Add a maxExpandedRecords+1 limit. If there are only maxExpandedRecords records these are returned. // If there are maxExpandedRecords+1 or more records an error will be thrown // With OData V2 its not possible to restrict the number of expanded records subSelect.addLimit(maxExpandedRecords + 1); } return subSelect; } function addSelectsForSelectFromTmp(select, dbSeg) { if (dbSeg.entityType.hasGeneratedKey()) { select.addSelects(dbSeg.getPropertiesForSelect(true)); } else { select.addSelects(dbSeg.getKeyProperties0123ForSelect(true)); select.addSelects(dbSeg.getPropertiesForSelect(true)); select.addSelects(dbSeg.getKeyPropertiesNotSelectedForSelect(true)); // SKDO for uri building } if (dbSeg._ExpandedNavigations.length > 0 || dbSeg._SelectedNavigations.length > 0) { select.addSelects(dbSeg.getNavPropertiesForSelect(true)); } } function addFromAndSortOrderForSelectFromTmp(select, rId) { select.setFrom({ schema: null, table: rId, alias: null }); /* no dbType required as property is used only for SortOrder */ select.addSortOrder(new edm.SortOrder(new edm.Property('1row', null), 'ASC')); }