UNPKG

@sap/xsodata

Version:

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

358 lines (280 loc) 16.5 kB
'use strict'; var sql = require('./sqlStatement'); var 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) { var 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) { var 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) { var 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'); var dbSegToBeUpdated = context.oData.links.toBeUpdated; var 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, isDeleteScenario); }; 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'); var 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'); var 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) { var dbSeg = sqlContext.dbSegToBeUpdated, linkDbSeg = sqlContext.context.oData.links.keySource, 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'); var dbSeg = sqlContext.dbSegToBeUpdated; var 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()); } var 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) { var dbSeg = (from === 'principal' ? sqlContext.dbSegPrincipal : sqlContext.dbSegDependent); var stm = new sql.Insert(); var 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'); var dbSeg = sqlContext.dbSegToBeUpdated; var 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'); var dbSeg = sqlContext.dbSegLast; // This is to have same 'context.sql.container' property in all tests sqlContext.context.sql.container = dbSeg.sql.stmContainer; var over = dbSeg.getOver().object; // e.g. "xsodata.test.tables::complex_assoc_atob" var 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'); var 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'); var dbSeg = sqlContext.dbSegLast; var stmCreate = new sql.Create(); stmCreate.setModifiers(['local', 'temporary', dbSeg.entityType.tableStoreType]); stmCreate.setTableName(rId); stmCreate.addProperties(dbSeg.getOverPropertiesForCreate()); return stmCreate; }