UNPKG

@sap/xsodata

Version:

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

1,009 lines (899 loc) 37.2 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) { const 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'); if ( dbSeg.restriction.onlyCount === true || dbSeg.restriction.onlyValue === true || 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; } } } 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) let dbSeg, isCount, select, 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 addQueryFilter(sqlContext, select, dbSeg); //add Query $orderby addQueryOrderBy(sqlContext, select, dbSeg, isCount); // group by & order by addGroupByAndOrderByForAggregates(select, dbSeg, isCount); //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) { // loop over all properties for (let 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) { let expression; // loop over all orders for (const treeOrder of tree.orders) { expression = treeOrder.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) { let seg1overProp, seg1parentsJoinProp, seg1childsJoinProp; let seg2overProp, seg2parentsJoinProp, seg2childsJoinProp; //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 (let 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 (let 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; for (let 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 const stm = new sql.Select(); let 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() ); } } if ( dbSeg._ExpandedNavigations.length > 0 || dbSeg._SelectedNavigations.length > 0 ) { stm.addSelects(dbSeg.getNavPropertiesForSelect()); } 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(), }); } const fromSource = dbSeg.getAliasedTableName(); 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 addQueryFilter(sqlContext, select, dbSeg) { 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); } } function addQueryOrderBy(sqlContext, select, dbSeg, isCount) { if (!isCount) { if (sqlContext.systemQueryParameters.orderby) { if ( !checkOrderbyProps( sqlContext.systemQueryParameters.orderby, dbSeg.entityType.propertiesMap ) ) { throw new BadRequest('Unknown orderby property'); } const 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); } } } function addGroupByAndOrderByForAggregates(select, dbSeg, isCount) { if (dbSeg.entityType.hasAggregates()) { select.addGroupBys( dbSeg.getAllSelectedNonAggregateIncludingJoinPropertiesForGroupBy() ); if (!isCount) { select.addSortOrders( dbSeg.getAllSelectedNonAggregatePropertiesForOrderBy( null, true ) ); } return; } 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 (const elem of select.select) { for (const ordered of orderByProperties) { if (ordered.getPropertyName() === elem.property) { elem.alias = ordered.expression.property; } } } select.addSortOrders(orderByProperties); } } } } 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'); const dbSeg = sqlContext.dbSegLast; const isCount = sqlContext.systemQueryParameters.inlinecount; const 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. const dbSeg = sqlContext.dbSegLast; const stmInsert = new sql.Insert(); stmInsert.setTableName({ table: dbSeg.sql.rId }); const selectRow = new sql.Select(); selectRow.addSelects(new sql.SelectFormula(null, '*')); selectRow.addSelects(new sql.OverFormula('1row')); const subSelect = exports._masterTableSimpleSelect(sqlContext); selectRow.setFromBySubQuery(subSelect); stmInsert.setSubSelect(selectRow); return stmInsert; } function masterTableSelect(sqlContext) { sqlContext.context.logger.debug('createGetStatements', 'masterTableSelect'); const dbSeg = sqlContext.dbSegLast; const stmSelect = new sql.Select(); const 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) { const navigationNames = parentSeg._ExpandedNavigations, truncateContainer = sqlContext.context.sql.cleanSessionTruncateContainer, dropContainer = sqlContext.context.sql.cleanSessionDropContainer; let truncateStmt; let dropStmt; //loop thought expanded tables for (const navigationName of navigationNames) { const dbSegExpanded = parentSeg.getRelevantNavigationSegments()[navigationName]; 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) { const 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) { const subSelect = exports._expandSimpleSelect(dbSeg, undefined, sqlContext); const 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) { const 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' ) ); const 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(), }); const onStatements = createOnStatements(dbSeg); if (onStatements.first.length > 0) { subSelect.addJoin( { table: dbSeg.getOver().object, alias: dbSeg.getAssocAlias(), }, onStatements.first ); } const 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()) { const 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) { const 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) { const parentsJoinProps = dbSeg.getFrom().joinproperties; const childJoinProps = dbSeg.getTo().joinproperties; const overJoinProps = dbSeg.getOver(); const 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) { const 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') ); }