UNPKG

@sap/xsodata

Version:

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

334 lines (301 loc) 11 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 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'); const container = context.sql.container; const stms = container.getInsertTmpStms(); return statementProcessor.execStmsAsPreparedNoResult( context, stms, asyncDone ); }; exports.select = function (context, asyncDone) { context.logger.silly('dataCollectorGet', 'select'); const container = context.sql.container; const 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 if (byPassInputs) { for (const input of byPassInputs) { let param = []; input.toSqlHana(context, param, { useDbType: input._dbType, }); for (const row of rows) { row[input.property] = param[0]; } } } context.logger.silly( 'GET dataCollector - row count', JSON.stringify(rows.length) ); 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) { const parameters = []; let 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'); const 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); }); };