jsharmony-db-iseries
Version:
jsHarmony Database Connector for DB2/iSeries
749 lines (671 loc) • 26.7 kB
JavaScript
/*
Copyright 2022 apHarmony
This file is part of jsHarmony.
jsHarmony is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
jsHarmony is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this package. If not, see <http://www.gnu.org/licenses/>.
*/
var DB = require('jsharmony-db');
var types = DB.types;
var typeHandler = require('./DB.iseries.types.js');
var odbc = require('odbc');
var async = require('async');
var _ = require('lodash');
var moment = require('moment');
var {performance, PerformanceObserver} = require('perf_hooks');
odbc.SQL_CHAR = 1;
// varchar 12 already defined
odbc.SQL_CLOB = -1;
odbc.SQL_NCHAR = -8;
odbc.SQL_NVARCHAR = -9;
odbc.SQL_NCLOB = -10;
// nchar and graphic have the same type code
var MAX_NETWORK_RETRY_COUNT = 3;
function DBdriver() {
this.name = 'iseries';
this.sql = require('./DB.iseries.sql.js');
this.meta = require('./DB.iseries.meta.js');
this.pools = []; /* { dbconfig: xxx, con: yyy } */
this.initStatements = [
"DECLARE GLOBAL TEMPORARY TABLE SESSION.JSHARMONY_META AS (SELECT 'USystem' CONTEXT FROM SYSIBM.SYSDUMMY1) WITH DATA WITH REPLACE",
];
var _this = this;
this.obs = new PerformanceObserver(function(list, observer) {
list.getEntries().forEach(function(entry) {
_this.platform.Log.info(entry.name + ': ' + entry.duration, { source: 'database' });
});
});
//Initialize platform
this.platform = {
Log: function(msg){ console.log(msg); }, // eslint-disable-line no-console
Config: {
debug_params: {
db_log_level: 6, //Bitmask: 2 = WARNING, 4 = NOTICES :: Database messages logged to the console / log
db_error_sql_state: false, //Log SQL state during DB error
db_perf_reporting: false // log performance metrics
}
}
};
this.platform.Log.info = function(msg){ console.log(msg); }; // eslint-disable-line no-console
this.platform.Log.warning = function(msg){ console.log(msg); }; // eslint-disable-line no-console
this.platform.Log.error = function(msg){ console.log(msg); }; // eslint-disable-line no-console
}
DBdriver.prototype.getDefaultSchema = function(){
return 'QGPL';
};
DBdriver.prototype.logRawSQL = function(sql){
if (this.platform.Config.debug_params && this.platform.Config.debug_params.db_raw_sql && this.platform.Log) {
this.platform.Log.info(sql, { source: 'database_raw_sql' });
}
};
function initDBConfig(dbconfig){
if(!dbconfig) return;
if(!dbconfig.options) dbconfig.options = {};
if(!dbconfig.options.pooled) dbconfig.options.pooled = false;
if(dbconfig.options.automatic_compound_commands !== false) dbconfig.options.automatic_compound_commands = true;
if(dbconfig.options.autotrim !== false) dbconfig.options.autotrim = true;
if(dbconfig.options.idle_disconnect !== false && typeof(dbconfig.options.idle_disconnect) != 'number') dbconfig.options.idle_disconnect = 30 * 60 * 1000;
}
DBdriver.prototype.togglePerformanceLogging = function() {
if (this.platform.Config.debug_params && this.platform.Config.debug_params.db_perf_reporting === true && this.platform.Log) {
this.obs.observe({entryTypes: ['measure']});
} else {
this.obs.disconnect();
}
};
DBdriver.prototype.getConnectionPool = function (dbconfig, onPool) {
if(!dbconfig) throw new Error('dbconfig is required');
var _this = this;
var odbcpool = null;
//Check if pool was already added
for(var i=0;i<this.pools.length;i++){
if(this.pools[i].dbconfig==dbconfig) odbcpool = this.pools[i];
}
//Add pool if it does not exist
if(!odbcpool){
_this.pools.push({
dbconfig: dbconfig,
pool: null,
pending: [],
idleTimer: null,
});
odbcpool = _this.pools[_this.pools.length - 1];
}
if(odbcpool.pool) {
resetIdleTimer(odbcpool);
return onPool(null, odbcpool.pool);
}
else if (odbcpool.pending.length > 0){
odbcpool.pending.push(onPool);
}
else { //Initialize pool connection if it was not initialized
performance.mark('odbc.pool start');
odbcpool.pending.push(onPool);
odbc.pool(dbconfig, function(err, pool) {
performance.mark('odbc.pool end');
performance.measure('odbc.pool', 'odbc.pool start', 'odbc.pool end');
if (err) {
pool.close();
pool = null;
}
odbcpool.pool = pool;
resetIdleTimer(odbcpool);
odbcpool.pending.forEach(function(f){ f(err, pool); });
odbcpool.pending = [];
});
}
};
function closePool(odbcpool, pool_cb) {
pool_cb = pool_cb || function() {};
if(odbcpool.idleTimer){
clearTimeout(odbcpool.idleTimer);
odbcpool.idleTimer = null;
}
if(!odbcpool.pool) return pool_cb();
performance.mark('pool.close start');
odbcpool.pool.close(function(){
performance.mark('pool.close end');
performance.measure('pool.close', 'pool.close start', 'pool.close end');
odbcpool.pool = null;
odbcpool.pending = [];
pool_cb();
});
}
function resetIdleTimer(odbcpool) {
if(odbcpool.idleTimer){
clearTimeout(odbcpool.idleTimer);
odbcpool.idleTimer = null;
}
if(!odbcpool.pool) return;
var idle_disconnect = odbcpool.dbconfig.options.idle_disconnect;
if ((typeof(idle_disconnect) == 'number') && (idle_disconnect > 0)) {
odbcpool.idleTimer = setTimeout(closePool, idle_disconnect, odbcpool);
}
}
DBdriver.prototype.Close = function(onClosed){
var _this = this;
async.each(_this.pools, closePool, onClosed);
};
DBdriver.prototype.getDBParam = function (dbtype, val) {
var _this = this;
if (!dbtype) throw new Error('Cannot get dbtype of null object');
if (val === null) return 'NULL';
if (typeof val === 'undefined') return 'NULL';
if ((dbtype.name == 'VarChar') || (dbtype.name == 'Char')) {
var valstr = val.toString();
if ((dbtype.length == types.MAX) || (dbtype.length == -1)) return "'" + _this.escape(valstr) + "'";
return "'" + _this.escape(valstr.substring(0, dbtype.length)) + "'";
}
else if (dbtype.name == 'VarBinary') {
var valbin = null;
if (val instanceof Buffer) valbin = val;
else valbin = Buffer.from(val.toString());
if (valbin.legth == 0) return "NULL";
return "BX'" + valbin.toString('hex').toUpperCase() + "'";
}
else if ((dbtype.name == 'BigInt') || (dbtype.name == 'Int') || (dbtype.name == 'SmallInt') || (dbtype.name == 'TinyInt')) {
var valint = parseInt(val);
if (isNaN(valint)) { return "NULL"; }
return valint.toString();
}
else if (dbtype.name == 'Boolean') {
if((val==='')||(typeof val == 'undefined')) return "NULL";
if(typeHandler.boolParser(val)) return '1';
return '0';
}
else if (dbtype.name == 'Decimal') {
let valfloat = parseFloat(val);
if (isNaN(valfloat)) { return "NULL"; }
return _this.escape(val.toString());
}
else if (dbtype.name == 'Float') {
let valfloat = parseFloat(val);
if (isNaN(valfloat)) { return "NULL"; }
return _this.escape(val.toString());
}
else if ((dbtype.name == 'Date') || (dbtype.name == 'Time') || (dbtype.name == 'DateTime')) {
var suffix = '';
var valdt = null;
if (val instanceof Date) { valdt = val; }
else if(_.isNumber(val) && !isNaN(val)){
valdt = moment(moment.utc(val).format('YYYY-MM-DDTHH:mm:ss.SSS'), "YYYY-MM-DDTHH:mm:ss.SSS").toDate();
}
else {
if (isNaN(Date.parse(val))) return "NULL";
valdt = new Date(val);
}
var mdate = moment(valdt);
if (!mdate.isValid()) return "NULL";
if(!_.isNumber(val) && !_.isString(val)){
if('jsh_utcOffset' in val){
//Time is in UTC, Offset specifies amount and timezone
var neg = false;
if(val.jsh_utcOffset < 0){ neg = true; }
suffix = moment.utc(new Date(val.jsh_utcOffset*(neg?-1:1)*60*1000)).format('HH:mm');
//Reverse offset
suffix = ' '+(neg?'+':'-')+suffix;
mdate = moment.utc(valdt);
mdate = mdate.add(val.jsh_utcOffset*-1, 'minutes');
}
if('jsh_microseconds' in val){
var ms_str = "000"+(Math.round(val.jsh_microseconds)).toString();
ms_str = ms_str.slice(-3);
suffix = ms_str.replace(/0+$/,'') + suffix;
}
}
var rslt = '';
if (dbtype.name == 'Date') rslt = "DATE('" + mdate.format('YYYY-MM-DD') + "')";
else if (dbtype.name == 'Time') rslt = "TIME('" + mdate.format('HH.mm.ss') + suffix + "')";
else rslt = "TIMESTAMP('" + mdate.format('YYYY-MM-DD HH:mm:ss.SSS') + suffix + "')";
return rslt;
}
else if ((dbtype.name == 'Raw')) {
return val.toString();
}
throw new Error('Invalid datatype: ' + JSON.stringify(dbtype));
};
var connectionId = 1;
DBdriver.prototype.ExecSession = function (dbtrans, dbconfig, session, callback, networkRetryCount) {
if(!dbconfig) throw new Error('dbconfig is required');
var _this = this;
networkRetryCount = networkRetryCount || 0;
if (dbtrans) {
session(null, dbtrans.con, [], callback);
}
else {
var preStatements = [];
var onConnect = function (err, con) {
if (err) { return _this.ExecError(err, callback, "DB Connect Error: "); }
if (!con.jshId) {
con.jshId = connectionId++;
preStatements = [];
if(dbconfig && dbconfig._presql) preStatements = preStatements.concat(splitSQL(dbconfig._presql));
}
//console.log('odbc connect', con.jshId);
session(null, con, preStatements, function (err) {
var args = Array.prototype.slice.call(arguments);
if (isNetworkError(err)) {
if (_this.platform.Config.debug_params.db_error_sql_state && !_this.silent) _this.platform.Log('Network connection error, resetting database connection', { source: 'database' });
return _this.Close(function() {
if ((networkRetryCount < MAX_NETWORK_RETRY_COUNT) && ((err.statementIndex||0) == 0)) {
setTimeout(function(){ _this.ExecSession(dbtrans, dbconfig, session, callback, networkRetryCount+1); });
} else {
callback.apply(null, args);
}
});
} else {
//console.log('odbc close', con.jshId);
performance.mark('con.close start');
con.close();
performance.mark('con.close end');
performance.measure('con.close', 'con.close start', 'con.close end');
callback.apply(null, args);
}
});
};
initDBConfig(dbconfig);
_this.togglePerformanceLogging();
if(dbconfig.options.pooled){
_this.getConnectionPool(dbconfig, function(err, pool) {
if (err) return onConnect(err);
performance.mark('pool.connect start');
pool.connect(function(err, con) {
performance.mark('pool.connect end');
performance.measure('pool.connect', 'pool.connect start', 'pool.connect end');
onConnect(err, con);
});
});
}
else {
performance.mark('odbc.connect start');
odbc.connect(dbconfig, function(err, con) {
performance.mark('odbc.connect end');
performance.measure('odbc.connect', 'odbc.connect start', 'odbc.connect end');
onConnect(err, con);
});
}
}
};
/*
https://www.ibm.com/docs/en/i/7.4?topic=reference-sql-messages-codes
https://www.ibm.com/docs/en/i/7.4?topic=application-db2-i-cli-sqlstate-values
01, is a warning.
HY, is generated by the CLI driver (either Db2 for i CLI or ODBC).
- If SQLCODE = 0 and SQLWARN0 is blank, execution was successful.
- If SQLCODE = 100, no data was found. For example, a FETCH statement
returned no data, because the cursor was positioned after the last row of the
result table.
- If SQLCODE > 0 and not = 100, execution was successful with a warning.
- If SQLCODE = 0 and SQLWARN0 = 'W', execution was successful with a
warning.
- If SQLCODE < 0, execution was not successful
[Error: [odbc] Error executing the sql statement] {
odbcErrors: [
{
state: 'HY000',
code: -104,
message: '[IBM][System i Access ODBC Driver][DB2 for i5/OS]SQL0104 - Token <END-OF-STATEMENT> was not valid. Valid tokens: + - AS <IDENTIFIER>.'
},
{
state: 'HY000',
code: 69898,
message: '[IBM][System i Access ODBC Driver][DB2 for i5/OS]PWS0005 - Error occurred in the database host server code.'
}
]
}
Gracefull connection closed:
[Error: Communication link failure. comm rc=10054 - CWBCO1047 - The IBM i server application disconnected the connection] {
odbcErrors: [
{
state: '08S01',
code: 10054,
message: '[IBM][System i Access ODBC Driver]Communication link failure. comm rc=10054 - CWBCO1047 - The IBM i server application disconnected the connection'
}
]
}
Server unreachable:
[Error: Communication link failure. comm rc=10060 - CWBCO1048 - A firewall blockage or time-out occurred trying to connect to the IBM i] {
odbcErrors: [
{
state: '08S01',
code: 10060,
message: '[IBM][System i Access ODBC Driver]Communication link failure. comm rc=10060 - CWBCO1048 - A firewall blockage or time-out occurred trying to connect to the IBM i'
}
]
}
Network cable pulled, two consecutive queries:
[Error: Communication link failure. comm rc=8413 - CWBCO1054 - A user-specified time-out occurred while sending or receiving data] {
odbcErrors: [
{
state: '08S01',
code: 8413,
message: '[IBM][System i Access ODBC Driver]Communication link failure. comm rc=8413 - CWBCO1054 - A user-specified time-out occurred while sending or receiving data'
}
]
}
[Error: Communication link failure. comm rc=11001 - CWBCO1004 - Remote address could not be resolved] {
odbcErrors: [
{
state: '08S01',
code: 11001,
message: '[IBM][System i Access ODBC Driver]Communication link failure. comm rc=11001 - CWBCO1004 - Remote address could not be resolved'
}
]
*/
var ibmErrorPrefix = /^(\[[^\]]+\])*/;
var networkErrorCodes = [
8413,
// https://www.ibm.com/docs/en/i/7.3?topic=codes-winsock-return
10024, // CWB_TOO_MANY_OPEN_SOCKETS
10035, // CWB_RESOURCE_TEMPORARILY_UNAVAILABLE
10038, // CWB_SOCKET_OPERATION_ON_NON_SOCKET
10047, // CWB_PROTOCOL_NOT_INSTALLED
10050, // CWB_NETWORK_IS_DOWN
10051, // CWB_NETWORK_IS_UNREACHABLE
10052, // CWB_NETWORK_DROPPED_CONNECTION_ON_RESET
10053, // CWB_SOFTWARE_CAUSED_CONNECTION_ABORT
10054, // CWB_CONNECTION_RESET_BY_PEER
10055, // CWB_NO_BUFFER_SPACE_AVAILABLE
10057, // CWB_SOCKET_IS_NOT_CONNECTED
10058, // CWB_CANNOT_SEND_AFTER_SOCKET_SHUTDOWN
10060, // CWB_CONNECTION_TIMED_OUT
10061, // CWB_CONNECTION_REFUSED
10064, // CWB_HOST_IS_DOWN
10065, // CWB_NO_ROUTE_TO_HOST
10091, // CWB_NETWORK_SUBSYSTEM_IS_UNAVAILABLE
10092, // CWB_WINSOCK_VERSION_NOT_SUPPORTED
11001, // CWB_HOST_DEFINITELY_NOT_FOUND
11002, // CWB_HOST_NOT_FOUND_BUT_WE_ARE_NOT_SURE
11004, // CWB_VALID_NAME_BUT_NO_DATA_RECORD
];
function isNetworkError(err) {
if (!err) return false;
if (!err.odbcErrors || !err.odbcErrors.length) return false;
for(var i=0; i<err.odbcErrors.length; i++){
if (networkErrorCodes.indexOf(err.odbcErrors[i].code) >= 0) return true;
}
return false;
}
DBdriver.prototype.ExecError = function(err, callback, errprefix, sql) {
if(err){
var errMsg = err.message || '';
var odbcErrors = [];
(err.odbcErrors || []).forEach(function(message) {
if (message.code == 69898) return;
if (message.code == -438) {
odbcErrors.push(message.message.replace(ibmErrorPrefix, '').replace('SQL0438 - ', ''));
return;
}
odbcErrors.push('('+message.code+') '+message.message.replace(ibmErrorPrefix, ''));
});
if (odbcErrors.length > 0) errMsg += (errMsg ? ' :: ' : '') + odbcErrors.join(' :: ');
err.message = errMsg;
}
if (this.platform.Config.debug_params.db_error_sql_state && !this.silent){
this.platform.Log((errprefix || '') + err.message + (sql ? ':: ' + sql + '\n' : ''), { source: 'database' });
}
if (callback) return callback(err, null);
else throw err;
};
DBdriver.prototype.ExecStatements = function(con, dbconfig, statements, callback) {
var _this = this;
var statementIndex = -1;
async.mapSeries(statements, function(sql, cb) {
statementIndex++;
_this.ExecStatement(con, dbconfig, sql, cb);
}, function(err, rslt) {
if (err) err.statementIndex = statementIndex;
var result = [];
var previous;
rslt.forEach(function(r) {
if (r && r.xrowcount && previous) {
return result.push([{xrowcount: previous.properties.count}]);
}
previous = r;
if (r && r.rows) {
return result.push(r.rows);
}
});
callback(err, result);
});
};
// response format:
/*
[
{ ONE: 1 },
statement: 'SELECT 1 AS ONE FROM SYSIBM.SYSDUMMY1',
parameters: [],
return: undefined,
count: 1,
columns: [
{
name: 'ONE',
dataType: 4,
columnSize: 10,
decimalDigits: 0,
nullable: false
}
]
]
*/
DBdriver.prototype.ExecStatement = function(con, dbconfig, sql, callback) {
var _this = this;
_this.logRawSQL(sql);
if (sql == 'return_row_count()') {
return setTimeout(callback, 0, null, {
xrowcount: true,
rows: null,
properties: { count: 1 },
});
}
performance.mark('con.query start');
con.query(sql, {cursor: true, fetchSize: 100}, function(queryErr, cursor) {
performance.mark('con.query end');
performance.measure('con.query', 'con.query start', 'con.query end');
if (queryErr) { return _this.ExecError(queryErr, callback, 'SQL Query Error: ', sql); }
var allResults = [];
async.doUntil(function iter(next) {
performance.mark('cursor.fetch start');
cursor.fetch(function(fetchErr, result) {
performance.mark('cursor.fetch end');
performance.measure('cursor.fetch', 'cursor.fetch start', 'cursor.fetch end');
if (fetchErr) { return _this.ExecError(fetchErr, callback, 'SQL Fetch Error: ', sql); }
allResults.push(result);
next(fetchErr);
});
}, function test(cb) {
if (cb) cb(null, cursor.noData); // most recent version of async
else return cursor.noData; // our specified version of async
}, function done(err) {
var result = {rows: null, properties: null};
if (allResults.length < 1) {
result.rows = null;
result.properties = null;
} else {
var first = allResults[0];
result.properties = first;
if (first.columns.length < 1) result.rows = null;
else {
result.rows = Array.prototype.concat.apply([], allResults);
}
}
if (result.rows && dbconfig.options.autotrim) trimCharacterFields(result.rows, result.properties.columns);
normalizeBigInt(result.rows, result.properties.columns);
performance.mark('cursor.close start');
cursor.close(function(closeErr) {
performance.mark('cursor.close end');
performance.measure('cursor.close', 'cursor.close start', 'cursor.close end');
performance.measure('fetch total', 'con.query end', 'cursor.close end');
//console.log(closeErr);
// closeError might indicate a problem for a non-select statement with no rows to fetch, but we don't have a good way to predict this ahead of time, so we can't really use it
callback(err, result);
});
});
});
};
function splitSQL(fsql){
var sql = [];
while(fsql){
var nexts = fsql.indexOf(';');
while((nexts > 0) && (fsql[nexts-1]=="\\")) nexts = fsql.indexOf(';', nexts+1);
if(nexts < 0){ sql.push(fsql.trim()); fsql = ''; }
else if(nexts==0) fsql = fsql.substr(1);
else{ sql.push(fsql.substr(0,nexts).trim()); fsql = fsql.substr(nexts+1); }
}
for(var i=0;i<sql.length;i++){
var stmt = sql[i].trim();
//Remove starting comments
while((stmt.indexOf('/*')==0)||(stmt.indexOf('//')==0)||(stmt.indexOf('--')==0)){
if((stmt.indexOf('//')==0)||(stmt.indexOf('--')==0)){
var eolpos = stmt.indexOf('\n');
if(eolpos >= 0) stmt = stmt.substr(eolpos+1);
else stmt = '';
}
else if(stmt.indexOf('/*')==0){
var eoc = stmt.indexOf('*/');
if(eoc >= 0) stmt = stmt.substr(eoc+2);
else stmt = '';
}
stmt = stmt.trim();
}
//Remove empty statements
var is_empty = stmt.match(/^(\s)*$/);
var is_comment = stmt.match(/^(\s)*\/\//);
is_comment = is_comment || stmt.match(/^(\s)*--/);
if(is_empty || is_comment){
sql.splice(i,1);
i--;
continue;
}
stmt = DB.util.ReplaceAll(stmt, "\\;", ';');
sql[i] = stmt;
}
return sql;
}
DBdriver._splitSQL = splitSQL;
DBdriver.prototype.Exec = function (dbtrans, context, return_type, sql, ptypes, params, callback, dbconfig) {
if(!dbconfig) throw new Error('dbconfig is required');
var _this = this;
var statements = [sql];
_this.ExecSession(dbtrans, dbconfig, function (err, con, preStatements, conComplete) {
if(dbtrans && (dbtrans.dbconfig != dbconfig)) err = new Error('Transaction cannot span multiple database connections');
if(err) return conComplete(err);
sql = _this.applySQLParams(sql, ptypes, params);
if (return_type == 'command' && dbconfig.options.automatic_compound_commands) {
sql = DB.util.ReplaceAll(sql, "\\;", ';');
statements = [ 'BEGIN ' + [].concat(preStatements, _this.getContextStatements(context, con), sql).join('; ') + (sql.endsWith(';') ? '' : ';') + ' END' ];
} else {
statements = splitSQL(sql);
statements = [].concat(preStatements, _this.getContextStatements(context, con), statements);
}
//console.log(params);
//console.log(ptypes);
//Execute sql
_this.ExecStatements(con, dbconfig, statements, conComplete);
}, function(err, rslt) {
if(err) {
if (callback != null) return callback(err, null);
else throw err;
}
var dbrslt = null;
//console.log(return_type, typeof(rslt), rslt);
if (return_type == 'row') { if (rslt[0] && rslt[0].length) dbrslt = rslt[0][0]; }
else if (return_type == 'recordset') dbrslt = rslt[0];
else if (return_type == 'multirecordset') dbrslt = rslt;
else if (return_type == 'scalar') {
if (rslt[0] && rslt[0].length) {
var row = rslt[0][0];
for (var key in row) {
if (Object.prototype.hasOwnProperty.call(row, key)) dbrslt = row[key];
}
}
}
var notices = [];
var warnings = [];
DB.util.LogDBResult(_this.platform, { sql: sql, dbrslt: dbrslt, notices: notices, warnings: warnings });
if (callback) callback(null, dbrslt, { notices: notices, warnings: warnings });
});
};
DBdriver.prototype.ExecTransTasks = function (execTasks, callback, dbconfig) {
if(!dbconfig) throw new Error('dbconfig is required');
var _this = this;
_this.ExecSession(null, dbconfig, function (err, con, preStatements, conComplete) {
if(err) return conComplete(err, null);
var trans = new DB.TransactionConnection(con,dbconfig);
trans.con.beginTransaction(function (err) {
if (err) return conComplete(err, null);
var exitTrans = function (dberr, rslt) {
if (dberr != null) { trans.con.rollback(function (err) { conComplete(dberr, null); }); }
else { trans.con.commit(function (err) { conComplete(err, rslt); }); }
};
_this.ExecStatements(con, dbconfig, preStatements, function(preerr, _) {
if (preerr) return exitTrans(preerr, null);
execTasks(trans, exitTrans);
});
});
}, callback);
};
DBdriver.prototype.escape = function(val){ return this.sql.escape(val); };
DBdriver.prototype.getContextStatements = function(context, con) {
if(!context) return [];
var _this = this;
var statements = [
"UPDATE SESSION.JSHARMONY_META SET CONTEXT = '"+this.escape(context)+"'",
];
if (!con.hasMeta) {
con.hasMeta = true;
statements = _this.initStatements.concat(statements);
}
return statements;
};
DBdriver.prototype.applySQLParams = function (sql, ptypes, params) {
var _this = this;
//Apply ptypes, params to SQL
var ptypes_ref = {};
if(ptypes){
var i = 0;
for (var p in params) {
ptypes_ref[p] = ptypes[i];
i++;
}
}
//Sort params by length
var param_keys = _.keys(params);
param_keys.sort(function (a, b) { return b.length - a.length; });
//Replace params in SQL statement
for (let i = 0; i < param_keys.length; i++) {
let p = param_keys[i];
var val = params[p];
if (val === '') val = null;
sql = DB.util.ReplaceAll(sql, '@' + p, _this.getDBParam(ptypes ? ptypes_ref[p] : types.fromValue(val), val));
}
return sql;
};
function trimCharacterFields(rows, columns) {
_.forEach(columns, function(col) {
if (col.dataType == odbc.SQL_CHAR || col.dataType == odbc.SQL_VARCHAR || col.dataType == odbc.CLOB || col.dataType == odbc.SQL_NCHAR || col.dataType == odbc.SQL_NVARCHAR || col.dataType == odbc.SQL_NCLOB) {
_.forEach(rows, function(row) {
if (row[col.name]) row[col.name] = row[col.name].trimEnd();
});
}
});
}
function normalizeBigInt(rows, columns) {
_.forEach(columns, function(col) {
var SQL_BIGINT = -5;
if (col.dataType == SQL_BIGINT) {
_.forEach(rows, function(row) {
if ((typeof row[col.name] != 'undefined') && (row[col.name] !== null)) row[col.name] = Number(row[col.name]);
});
}
});
}
DBdriver.trimCharacterFields = trimCharacterFields;
exports = module.exports = DBdriver;