UNPKG

@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
'use strict'; 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); }); };