@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
JavaScript
;
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;
}