@sap/xsodata
Version:
Expose data from a HANA database as OData V2 service with help of .xsodata files.
265 lines (225 loc) • 9.66 kB
JavaScript
;
const async = require('async');
const dataCollector2 = require('./dataCollector2');
const sqlStatement = require('./sqlStatement');
const statementProcessor = require('./statementProcessor');
const utils = require('../utils/utils');
const SqlError = require('../utils/errors/sqlError');
const BadRequest = require('../utils/errors/http/badRequest');
// const { createNewContentIdFromDbSegSelectedRows } = require('../processor/contentIdHelper');
// const { call } = require('../../test/utils');
const saveTmpTableCreate = function(item, context, asyncDone) {
const dropTmpTableCallback = (err, context) => {
if (err) {
return asyncDone(err, context);
}
return saveTmpTableReCreateCallback( item, context, asyncDone );
};
const saveTmpTableCreateCallback = (err, context) => {
if (err && err.cause && err.cause.code === 288) {
// duplicate table name: try to drop temporary table
let dropStatement = new sqlStatement.Drop(item.table);
return statementProcessor.execStmsDirectlyNoResult(context, dropStatement, dropTmpTableCallback);
}
return asyncDone(err, context);
};
return statementProcessor.execStmsDirectlyNoResult(context, item, saveTmpTableCreateCallback);
};
const saveTmpTableReCreateCallback = function(item, context, asyncDone) {
return statementProcessor.execStmsDirectlyNoResult(context, item, asyncDone);
};
exports.createTmpTables = function (context, asyncDone) {
context.logger.silly('dataCollectorGet', 'createTmpTables');
let tmpCreateStmts = context.sql.container.getCreateTmpStms();
async.mapSeries(
tmpCreateStmts,
function(item, callback) {
saveTmpTableCreate(item, context, callback);
},
function (err) {
return asyncDone(err, context);
}
);
};
exports.insertFillTmpTables = function (context, asyncDone) {
context.logger.silly('dataCollectorGet', 'insertFillTmpTables');
var container = context.sql.container;
var stms = container.getInsertTmpStms();
return statementProcessor.execStmsAsPreparedNoResult(context, stms, asyncDone);
};
exports.select = function (context, asyncDone) {
context.logger.silly('dataCollectorGet', 'select');
var container = context.sql.container;
var stms = container.select.concat(container.selectTmp);
async.mapSeries(
stms,
function (item, cb) {
try {
execStatement(item, function (err, rows) {
if (err) {
return cb(err);
}
if (rows.length === 0 && item.stm.getFallbackStatement && item.stm.getFallbackStatement()) {
return execStatement({
stm: item.stm.getFallbackStatement(),
dbSeg: item.dbSeg
}, cb, true);
}
if (item.stm.getCustomData) {
const maxRecords = item.stm.getCustomData ? item.stm.getCustomData('maxRecords') : null;
if (maxRecords) {
context.logger.info('SQL Exec', 'Check maxRecords :' + maxRecords);
if (rows.length > maxRecords) {
// since there are possibly more
return cb(new BadRequest("Too many records in result set. Use $top to reduce the record count.", context));
}
}
const maxExpandedRecords = item.stm.getCustomData ? item.stm.getCustomData('maxExpandedRecords') : null;
if (maxExpandedRecords) {
context.logger.info('SQL Exec', 'Check maxExpandedRecords :' + maxExpandedRecords);
if (rows.length > maxExpandedRecords) {
// since there are possibly more
return cb(new BadRequest("Too many records in expanded result set.", context));
}
}
}
return cb(null, rows);
});
} catch (ex) {
cb(ex);
}
}, function (err) {
if (err) {
return asyncDone(err, context);
}
return asyncDone(err, context);
}
);
function execStatement(item, cb, count) {
let p = [];
context.logger.debug('dataCollector', 'count fallback: ' + !!count);
context.logger.silly('dataCollector', 'pre to hana');
let sql = item.stm.toSqlHana(new sqlStatement.SqlBuildHanaContext(context), p);
const byPassInputs = item.stm.byPassInputs;
dataCollector2.executeSqlAsPreparedStatement(context, sql, p, (err, rows) => {
if (err) {
if (count) {
return CalcViewCallback(item, context, cb);
} else {
context.logger.info('SQL Exec', 'Error: \n' + err);
context.logger.info('SQL Exec', 'SQL: \n' + sql);
return cb(new SqlError(context, err));
}
}
if (!count) {
item.dbSeg.sql.rows = rows;
item.dbSeg.sql.countRows = [];
if (rows.length === 1 && utils.isETagHeaderRequired(context, item.dbSeg)) {
item.dbSeg.etagHeader = rows[0].__etag;
}
} else {
item.dbSeg.sql.rows = [];
item.dbSeg.sql.countRows = rows;
}
// Add byPassInputs to rows
let conv = [];
if (byPassInputs) {
for (let i = 0; i < byPassInputs.length; i++) {
const input = byPassInputs[i];
let param = [];
input.toSqlHana(context, param, { useDbType: input._dbType });
conv.push(param[0]);
for (let r = 0; r < rows.length; r++) {
let row = rows[r];
row[input.property] = param[0];
}
}
}
context.logger.silly('GET dataCollector - row count', JSON.stringify(rows.length));
// context.logger.silly('dataCollector', JSON.stringify(rows, null, 2));
return cb(null, rows);
});
}
};
let CalcViewCallback = function (item, context, cb) {
// try workaround for calcviews with transparent filters
if (!item.stm.froms || !item.stm.froms[0]) {
return cb(new Error('Calcview fallback failed'));
}
let subStatement = item.stm.froms[0].subQuery;
let p = [];
let sql = subStatement.toSqlHana(new sqlStatement.SqlBuildHanaContext(context), p);
let start = 0;
let top = 10;
let readNext = true;
let rowcount = 0;
async.whilst(
function () { return readNext; },
function (callback) {
let sqlIter = sql + " LIMIT " + top + ' OFFSET ' + start;
context.logger.debug('SQL Exec CBF', 'SQL: \n' + sqlIter);
dataCollector2.executeSqlAsPreparedStatement(context, sqlIter, p, (err, rows) => {
if (rows.length === 0) {
readNext = false; //exit
} else {
rowcount += rows.length;
}
start += top;
return callback(null, start);
});
},
function (err, start) {
if (err) {
context.logger.info('SQL Exec CBF', 'Error: \n' + err);
context.logger.info('SQL Exec CBF', 'at iteration' + start);
return cb(err);
}
item.dbSeg.sql.countRows = [{ 'c': rowcount }];
return cb(null, item.dbSeg.sql.countRows);
}
);
};
/**
* Executes SQL SELECT statement.
*
* @param {Object} context - OData context
* @param {Object} selectStmt - instance of Select "class" from sqlStatement.js
* @param {Function} callback - callback, which should be called once the operation is completed
*/
exports.executeSelect = function executeSelect(context, selectStmt, callback) {
var parameters = [];
var sql;
try {
sql = selectStmt.toSqlHana(new sqlStatement.SqlBuildHanaContext(context), parameters);
} catch (e) {
return callback(e);
}
dataCollector2.executeSqlAsPreparedStatement(context, sql, parameters, (err, rows) => {
callback(err, rows);
});
};
/**
* Executes truncation of temporary created tables
*
* @param {Object} context The xsodata context
* @param {Function} asyncDone async waterfall callback
*/
exports.truncateTempTables = function (context, asyncDone) {
context.logger.silly('dataCollectorGet', 'truncateTempTables');
statementProcessor.cleanSessionTruncateContainer(context, asyncDone);
};
exports.dropTempTables = function (context, asyncDone) {
context.logger.silly('dataCollectorGet', 'dropTempTables');
statementProcessor.cleanSessionDropContainer(context, asyncDone);
};
exports.commit = function (context, asyncDone) {
context.logger.debug('dataCollectorGet', 'commit');
var client = context.db.client;
client.commit(function (err) {
if (err) {
context.logger.info('SQL Exec', 'Commit Error: \n' + JSON.stringify(err));
return asyncDone(err, context);
}
return asyncDone(null, context);
});
};