UNPKG

@sap/xsodata

Version:

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

551 lines (475 loc) 18.7 kB
'use strict'; const sql = require('./sqlStatement'); const sqlTools = require('./sqlTools'); function masterTableCreate(sqlContext, rId) { return createSQLCreateStmt(sqlContext.dbSegToBeUpdated, rId, true); } /** * Creates SQL CREATE TEMPORARY TABLE statement for the association table. sqlContext * @param {Object} sqlContext - table name * @param {string} rId - table name * @param {string} from - type of the DBSegment, which should be used as a template for the temporary table. * The parameter can have one of the two values, either 'principal' or 'dependent'. * @returns {Create} SQL CREATE statement */ function createSQLCreateForAssocTable(sqlContext, rId, from) { const dbSeg = from === 'principal' ? sqlContext.dbSegPrincipal : sqlContext.dbSegDependent; return createSQLCreateStmt(dbSeg, rId, false); } /** * Creates SQL CREATE TEMPORARY TABLE statement. * @param {object} dbSeg - DBSegment * @param {string} rId - table name * @param {boolean} withForeignKeyProperties - indicates whether the created template should contain foreign key * properties or non-key properties. * @returns {Create} SQL CREATE TEMPORARY TABLE statement */ function createSQLCreateStmt(dbSeg, rId, withForeignKeyProperties) { const stmCreate = new sql.Create(); stmCreate.setModifiers([ 'local', 'temporary', dbSeg.entityType.tableStoreType, ]); stmCreate.setTableName(rId); stmCreate.setAs(createSelectTemplate(dbSeg, withForeignKeyProperties)); // Note about the modifier 'WITHOUT CONSTRAINT' for create temporary table: // Is only important in HANA Cloud databases, but is also valid in HANA Service databases. // => Characteristics of creating temporary tables: // (1) HANA Service: Temporary tables do not have constraints (e.g. NOT-NULL for key columns) // (2) HANA Cloud: Temporary tables have constraints, if created via this SQL: // "create local temporary <store-type> <tab-name> as (select ... from <source-table> ...)" // => temp. table inherits the constraints from <source-table> // => use the modifier 'WITHOUT CONSTRAINT' to switch off this inheritance // Test cases affected for HANA Cloud: // - test_apps/test_authorization/test_authorization_scopes_batch.js // - test_apps/test_authorization/test_authorization_scopes.js // - test_apps/test_db/test_clean_dbcon_after_xsodata_processing.js // - test_apps/test_xsodata/test_links.js // - test_apps/test_xsodata/test_linksBatch.js stmCreate.setPostModifiers(['with no data', 'WITHOUT CONSTRAINT']); return stmCreate; } /** * Creates SQL SELECT statement, which should be used as a template for the SQL CREATE TEMPORARY TABLE statement. * @param {object} dbSeg - DBSegment representing the DB table * @param {boolean} withForeignKeyProperties - indicates whether the created template should contain foreign key * properties or non-key properties. * @returns {Select} SQL SELECT statement. */ function createSelectTemplate(dbSeg, withForeignKeyProperties) { const stm = new sql.Select(); stm.addSelects(dbSeg.getQKeyProperties()); if (dbSeg._ExpandedNavigations.length > 0) { stm.addSelects(dbSeg.getNonKeyNonSelectedProperties4Expansion()); } if (withForeignKeyProperties) { stm.addSelects(dbSeg.getQForeignKeyProperties()); } else { stm.addSelects(dbSeg.getQNonKeyProperties()); } stm.setFrom(dbSeg.getAliasedTableName()); return stm; } exports.createLinksStatementsCreateTmpTables = function ( context, isDeleteScenario ) { context.logger.silly('createLinksSQLStatements', 'createLinksStatements'); const dbSegToBeUpdated = context.oData.links.toBeUpdated; const sqlContext = { context: context, // sqlContext gets parent context (includes "isHanaCloudDb") netId: context.uniqueNetworkRequestID, reqId: context.uniqueRequestID, rId: 1, dbSegToBeUpdated: dbSegToBeUpdated, dbSegPrincipal: context.oData.links.principal, dbSegDependent: context.oData.links.dependent, systemQueryParameters: context.oData.systemQueryParameters, }; if (isDeleteScenario) { sqlContext.dbSegKeySource = context.oData.links.keySource; } context.sql = sqlContext; dbSegToBeUpdated.sql.stmContainer = new sql.PutContainer(); createStatementContainerCreateTmpTables(sqlContext); }; exports.createLinksStatementsInsert = function (context, isDeleteScenario) { context.logger.silly('createLinksSQLStatements', 'createLinksStatements'); const sqlContext = context.sql; createStatementContainerInsert(sqlContext, isDeleteScenario); }; function createStatementContainerCreateTmpTables( sqlContext /*, isDeleteScenario*/ ) { sqlContext.context.logger.debug( 'createLinksSQLStatements', 'createStatementContainer' ); const dbSeg = sqlContext.dbSegToBeUpdated; // This is to have same 'context.sql.container' property in all tests sqlContext.context.sql.container = dbSeg.sql.stmContainer; dbSeg.sql.rIdNew = sqlTools.rIdToNewTableName( dbSeg._Alias, sqlContext.netId, sqlContext.reqId, sqlContext.rId++ ); dbSeg.sql.rIdOld = sqlTools.rIdToOldTableName( dbSeg._Alias, sqlContext.netId, sqlContext.reqId, sqlContext.rId++ ); // principal and dependent (temp) tables, to be used in custom exits dbSeg.sql.rIdPrincipal = sqlTools.rIdToAlteredTableName( '#p', sqlContext.dbSegPrincipal._AliasName, sqlContext.netId, sqlContext.reqId, sqlContext.rId++ ); dbSeg.sql.rIdDependent = sqlTools.rIdToAlteredTableName( '#d', sqlContext.dbSegDependent._AliasName, sqlContext.netId, sqlContext.reqId, sqlContext.rId++ ); //create statement for master table dbSeg.sql.stmContainer.createTmp = masterTableCreate( sqlContext, dbSeg.sql.rIdNew ); dbSeg.sql.stmContainer.createTmpOld = masterTableCreate( sqlContext, dbSeg.sql.rIdOld ); //create statements for principal/dependent tables dbSeg.sql.stmContainer.createPrincipal = createSQLCreateForAssocTable( sqlContext, dbSeg.sql.rIdPrincipal, 'principal' ); dbSeg.sql.stmContainer.createDependent = createSQLCreateForAssocTable( sqlContext, dbSeg.sql.rIdDependent, 'dependent' ); if (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; dbSeg.sql.stmContainer.createTmpTruncate = sql.buildTableStatement( sql.Truncate, sqlContext, dbSeg.sql.stmContainer.createTmp.table ); dbSeg.sql.stmContainer.createTmpOldTruncate = sql.buildTableStatement( sql.Truncate, sqlContext, dbSeg.sql.stmContainer.createTmpOld.table ); dbSeg.sql.stmContainer.createPrincipalTruncate = sql.buildTableStatement( sql.Truncate, sqlContext, dbSeg.sql.stmContainer.createPrincipal.table ); dbSeg.sql.stmContainer.createDependentTruncate = sql.buildTableStatement( sql.Truncate, sqlContext, dbSeg.sql.stmContainer.createDependent.table ); // Build the drop statement for created temporary table; dbSeg.sql.stmContainer.createTmpDrop = sql.buildTableStatement( sql.Drop, sqlContext, dbSeg.sql.stmContainer.createTmp.table ); dbSeg.sql.stmContainer.createTmpOldDrop = sql.buildTableStatement( sql.Drop, sqlContext, dbSeg.sql.stmContainer.createTmpOld.table ); dbSeg.sql.stmContainer.createPrincipalDrop = sql.buildTableStatement( sql.Drop, sqlContext, dbSeg.sql.stmContainer.createPrincipal.table ); dbSeg.sql.stmContainer.createDependentDrop = sql.buildTableStatement( sql.Drop, sqlContext, dbSeg.sql.stmContainer.createDependent.table ); } } function createStatementContainerInsert(sqlContext, isDeleteScenario) { sqlContext.context.logger.debug( 'createLinksSQLStatements', 'createStatementContainer' ); const dbSeg = sqlContext.dbSegToBeUpdated; //create insert statement for master table dbSeg.sql.stmContainer.insertTmp = masterTableInsertToNew( sqlContext, dbSeg.sql.rIdNew, isDeleteScenario ); //create insert statement for master table dbSeg.sql.stmContainer.insertTmpOld = masterTableInsertToOld( sqlContext, dbSeg.sql.rIdOld ); //create insert statements for principal/dependent tables dbSeg.sql.stmContainer.insertPrincipal = createInsertStmtForAssocTable( sqlContext, dbSeg.sql.rIdPrincipal, 'principal' ); dbSeg.sql.stmContainer.insertDependent = createInsertStmtForAssocTable( sqlContext, dbSeg.sql.rIdDependent, 'dependent' ); //create select statement for master table dbSeg.sql.stmContainer.updateReal = masterTableUpdate( sqlContext, dbSeg.sql.rIdNew ); } function masterTableInsertToNew(sqlContext, rId, isDeleteScenario) { const dbSeg = sqlContext.dbSegToBeUpdated, linkDbSeg = sqlContext.context.oData.links.keySource; let subSelect, dbValues, insertStmt; sqlContext.context.logger.debug( 'createLinksSQLStatements', 'masterTableInsertToNew' ); insertStmt = new sql.Insert(); insertStmt.setTableName({ table: rId }); insertStmt.addNames(dbSeg.getQKeyProperties()); insertStmt.addNames(dbSeg.getQForeignKeyProperties()); if (isDeleteScenario) { return insertStmt; } subSelect = new sql.Select(); dbValues = dbSeg.getQKeyWithValuesDB().map(function (edmValue) { return edmValue.value; }); subSelect.addSelects(dbValues); subSelect.addSelects(linkDbSeg.getQKeyProperties()); subSelect.setFrom(linkDbSeg.getAliasedTableName()); subSelect.addWhereKeyValuePairs(linkDbSeg.getQKeyWithValuesDB()); insertStmt.setSubSelect(subSelect); return insertStmt; } function masterTableInsertToOld(sqlContext, rId) { sqlContext.context.logger.debug( 'createLinksSQLStatements', 'masterTableInsertToOld' ); const dbSeg = sqlContext.dbSegToBeUpdated; const subSelect = new sql.Select(); subSelect.addSelects(dbSeg.getQKeyProperties()); subSelect.addSelects(dbSeg.getQForeignKeyProperties()); subSelect.setFrom(dbSeg.getAliasedTableName()); subSelect.addWhereKeyValuePairs(dbSeg.getQKeyWithValuesDB()); if (sqlContext.dbSegKeySource && sqlContext.dbSegKeySource.hasKeyValues()) { subSelect.addWhereKeyValuePairs(dbSeg.getQForeignKeyWithValues()); } const stm = new sql.Insert(); stm.setTableName({ table: rId }); stm.setSubSelect(subSelect); return stm; } /** * Creates SQL INSERT statement for the association table. * @param {Object} sqlContext - table name * @param {string} rId - table name * @param {string} from - type of the DBSegment, which should be used to create the SQL INSERT statement. * The parameter can have one of the two values, either 'principal' or 'dependent'. * @returns {Insert} SQL INSERT statement */ function createInsertStmtForAssocTable(sqlContext, rId, from) { const dbSeg = from === 'principal' ? sqlContext.dbSegPrincipal : sqlContext.dbSegDependent; const stm = new sql.Insert(); const subSelect = new sql.Select(); subSelect.addSelects(dbSeg.getQKeyProperties()); subSelect.addSelects(dbSeg.getQNonKeyProperties()); subSelect.setFrom(dbSeg.getAliasedTableName()); subSelect.addWhereKeyValuePairs(dbSeg.getQKeyWithValuesDB()); stm.setTableName({ table: rId }); stm.setSubSelect(subSelect); return stm; } /** * Create select SQL statement for master table * @param sqlContext * @param rId * @returns {exports.Select} */ function masterTableUpdate(sqlContext, rId) { sqlContext.context.logger.debug( 'createLinksSQLStatements', 'masterTableUpdate' ); const dbSeg = sqlContext.dbSegToBeUpdated; const update = new sql.Update(); update.setTable(dbSeg.getAliasedTableName('PERM')); //overwrite alias update.addSetCopyProperties(dbSeg.getQForeignKeyProperties(), 'TEMP'); update.setFrom({ table: rId, alias: 'TEMP', }); update.addWhereKeyValuePairs(dbSeg.getQKeyWithValues('PERM')); if (sqlContext.dbSegKeySource && sqlContext.dbSegKeySource.hasKeyValues()) { update.addWhereKeyValuePairs(dbSeg.getQForeignKeyWithValues('PERM')); } return update; } /******************** MxN relationships ***********************/ exports.createMNSQLContext = function createMNSQLContext(context, dbSegment) { return { context: context, netId: context.uniqueNetworkRequestID, reqId: context.uniqueRequestID, rId: 1, dbSegLast: dbSegment, dbSegPrincipal: context.oData.links.principal, dbSegDependent: context.oData.links.dependent, systemQueryParameters: context.oData.systemQueryParameters, }; }; exports.createMNStatementContainerCreateTmpTables = function createMNStatementContainerCreateTmpTable(sqlContext) { sqlContext.context.logger.debug( 'createLinksSQLStatements', 'createMNStatementContainer' ); const dbSeg = sqlContext.dbSegLast; // This is to have same 'context.sql.container' property in all tests sqlContext.context.sql.container = dbSeg.sql.stmContainer; const over = dbSeg.getOver().object; // e.g. "xsodata.test.tables::complex_assoc_atob" const alias = over.substring(over.indexOf('::') + 2, over.length); dbSeg.sql.rId = sqlTools.rIdToNewTableName( alias, sqlContext.netId, sqlContext.reqId, sqlContext.rId++ ); // principal and dependent (temp) tables, to be used in custom exits dbSeg.sql.rIdPrincipal = sqlTools.rIdToAlteredTableName( '#p', sqlContext.dbSegPrincipal._AliasName, sqlContext.netId, sqlContext.reqId, sqlContext.rId++ ); dbSeg.sql.rIdDependent = sqlTools.rIdToAlteredTableName( '#d', sqlContext.dbSegDependent._AliasName, sqlContext.netId, sqlContext.reqId, sqlContext.rId++ ); //create statement for master table dbSeg.sql.stmContainer.createTmp = masterMNTableCreate( sqlContext, dbSeg.sql.rId ); //create statements for principal/dependent tables dbSeg.sql.stmContainer.createPrincipal = createSQLCreateForAssocTable( sqlContext, dbSeg.sql.rIdPrincipal, 'principal' ); dbSeg.sql.stmContainer.createDependent = createSQLCreateForAssocTable( sqlContext, dbSeg.sql.rIdDependent, 'dependent' ); if (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; dbSeg.sql.stmContainer.createTmpTruncate = sql.buildTableStatement( sql.Truncate, sqlContext, dbSeg.sql.stmContainer.createTmp.table ); dbSeg.sql.stmContainer.createPrincipalTruncate = sql.buildTableStatement( sql.Truncate, sqlContext, dbSeg.sql.stmContainer.createPrincipal.table ); dbSeg.sql.stmContainer.createDependentTruncate = sql.buildTableStatement( sql.Truncate, sqlContext, dbSeg.sql.stmContainer.createDependent.table ); // Build the drop statement for created temporary table; dbSeg.sql.stmContainer.createTmpDrop = sql.buildTableStatement( sql.Drop, sqlContext, dbSeg.sql.stmContainer.createTmp.table ); dbSeg.sql.stmContainer.createPrincipalDrop = sql.buildTableStatement( sql.Drop, sqlContext, dbSeg.sql.stmContainer.createPrincipal.table ); dbSeg.sql.stmContainer.createDependentDrop = sql.buildTableStatement( sql.Drop, sqlContext, dbSeg.sql.stmContainer.createDependent.table ); } }; exports.createMNStatementContainerInsert = function createMNStatementContainerInsert(sqlContext) { sqlContext.context.logger.debug( 'createLinksSQLStatements', 'createMNStatementContainer' ); const dbSeg = sqlContext.dbSegLast; //create insert statements for principal/dependent tables dbSeg.sql.stmContainer.insertPrincipal = createInsertStmtForAssocTable( sqlContext, dbSeg.sql.rIdPrincipal, 'principal' ); dbSeg.sql.stmContainer.insertDependent = createInsertStmtForAssocTable( sqlContext, dbSeg.sql.rIdDependent, 'dependent' ); }; function masterMNTableCreate(sqlContext, rId) { sqlContext.context.logger.debug( 'createLinksSQLStatements', 'masterMNTableCreate' ); const dbSeg = sqlContext.dbSegLast; const stmCreate = new sql.Create(); stmCreate.setModifiers([ 'local', 'temporary', dbSeg.entityType.tableStoreType, ]); stmCreate.setTableName(rId); stmCreate.addProperties(dbSeg.getOverPropertiesForCreate()); return stmCreate; }