enhancer-data-bridge
Version:
A bridge between Enhancer Clould and user business datasource
419 lines (377 loc) • 13.5 kB
JavaScript
;
var BaseServiceClass = require('./service-base');
var async = require('async');
var mssql = require('mssql');
var escape = require('mysql').escape;
var myescape = function(val) {
val = escape(val) + '';
return val.replace(/\\'/g, "''")
};
var escapeId = require('mysql').escapeId;
var myescapeId = function(val) {
if (!val) {
return '';
}
val = escapeId(val) + '';
val = val.replace(/^\`/, '[').replace(/\`$/, ']');
return val;
}
var varTypeMap = {
bit: "number",
int: "number",
smallint: "number",
tinyint: "number",
numeric: "number",
decimal: "number",
money: "number",
smallmoney: "number",
float: "number",
real: "number",
datetime: "string",
timestamp: "string",
char: "string",
varchar: "string",
text: "string",
nchar: "string",
nvarchar: "string",
ntext: "string",
binary: "string",
varbinary: "string",
image: "string"
};
class MssqlService extends BaseServiceClass {
constructor(dbConfig) {
super(dbConfig);
dbConfig.server = dbConfig.host;
dbConfig.max = dbConfig.connectionLimit;
dbConfig.connectionTimeout = parseInt(dbConfig.acquireTimeout);
if (dbConfig.connectionTimeout < 5000) {
dbConfig.connectionTimeout = 5000;
}
dbConfig.port = parseInt(dbConfig.port);
dbConfig.encrypt = dbConfig.encrypt || false;
dbConfig.requestTimeout = 600000;
this.dbConfig = dbConfig;
}
/**
* @overridden BaseServiceClass#criteriaQuery.
* @param criteria {Object} Query criteria
* eg: {
id: "d101",
query: "SELECT * FROM TABLE_$curr_year$ WHERE UID = @user_id@ AND NUM = @12-number@",
serverVars: {...}
params: {"1-userid": "zyz"},
countRecords: true || false,
metaData: true || false
filter: {...}, // @see this.__parseFiltersToSqlConditions()
format: "object" || "array",
paged: false || true,
page: 0,
rownum: 10,
sortBy: "uid asc, num desc"
}
* @param callback {Function}
- err
- result
*/
criteriaQuery(criteria, callback) {
var dbConfig = this.dbConfig;
var that = this;
var statement = this.prepareSQLStatement(criteria, '?');
var sql = statement.sql.replace(/\\'/g, "''");
var params = statement.params;
var pool;
var ROWS_LENGTH = 'ROWS_LENGTH';
var countRecords = function(cb) {
if (!criteria.paged || !criteria.countRecords || !statement.isSelect) {
return cb(null, ROWS_LENGTH);
}
var countSql = that.getCountSql(sql, params);
var req = pool.request();
// input parameter
var i = 0;
countSql.sql = countSql.sql.replace(/\?/g, function(id) {
var pname = '@param' + i;
var val = countSql.params[i];
// Handle array specially for batch operations
if (val instanceof Array) {
i++;
return ' ' + myescape(val) + ' ';
}
req.input('param' + i, val);
i++;
return pname;
});
req.query(countSql.sql, (err, result) => {
if (err) {
return cb(err);
}
if (!result.recordset.length) {
return cb(null, 0);
}
cb(null, result.recordset[0].records);
});
};
var fetchData = function(cb) {
if (statement.isSelect && criteria.paged === true ) {
criteria.rowNum = parseInt(criteria.rowNum) || 1;
var start = ( criteria.page - 1 ) * criteria.rowNum + 1;
var end = start + criteria.rowNum - 1;
sql = sql.replace(/^\s*SELECT\s/i, function(s) {
return 'SELECT ROW_NUMBER() OVER (ORDER BY '
+ (criteria.sortBy || '(SELECT NULL)')
+ ') __RN, ';
});
sql = 'SELECT * FROM (' + sql
+ ') AS TAB1 WHERE __RN BETWEEN ' + start + ' AND ' + end;
} else if (statement.isSelect && criteria.sortBy ) {
sql = sql + " ORDER BY " + criteria.sortBy;
}
var req = pool.request();
// input parameter
var i = 0;
sql = sql.replace(/\?/g, function(id) {
var pname = '@param' + i;
var val = params[i];
// Handle array specially for batch operations
if (val instanceof Array) {
i++;
return ' ' + myescape(val) + ' ';
}
req.input('param' + i, val);
i++;
return pname;
});
req.query(sql, (err, data) => {
if (err) {
return cb(err);
}
var length = data.recordsets.length;
var set = data.recordsets[length - 1];
var columns = (set || {}).columns;
if ( criteria.paged === true && set ) {
set = set.map(function(r) {
delete r.__RN;
return r;
});
}
var result = {
rows: set,
paged: criteria.paged,
page: criteria.page,
rowNum: criteria.rowNum
};
if (criteria.metaData) {
result.metaData = (function(columns) {
var fields = [];
var col;
for ( i in columns ) {
col = columns[i];
col.dbType = col.type.declaration;
col.varType = varTypeMap[col.dbType.toLowerCase()] || 'string';
fields.push(col);
}
return fields;
})(columns);
}
cb(null, result);
});
};
// Create connection and destroy it after use. This is just for preview mode,
// and different from connection using method in bodhi app.
var pool = new mssql.ConnectionPool(dbConfig);
pool.connect(err => {
if (err) {
return callback(err);
}
async.parallel([countRecords, fetchData], function(err, results) {
if (err) {
err.sql = sql;
return callback(err);
}
pool.close();
var records = results[0];
var finalResult = results[1];
finalResult.records = records === ROWS_LENGTH
? finalResult.rows.length
: records;
if (criteria.format === 'array') {
finalResult.rows = finalResult.rows.map(function(obj) {
var arr = [];
for (var i in obj) {
arr.push(obj[i]);
}
return arr;
});
}
callback(null, finalResult);
});
});
}
execute(sql, params, cb) {
var that = this;
var dbConfig = that.dbConfig;
const pool = new mssql.ConnectionPool(dbConfig);
pool.connect(err => {
if (err) {
return cb(err);
}
var req = pool.request();
var i = 0;
var val;
sql = sql.replace(/\?\??/g, function(id) {
val = params[i];
if (/\?\?/.test(id)) {
i++;
return ' ' + myescapeId(val) + ' ';
}
// Handle array specially for batch operations
if (val instanceof Array) {
i++;
return ' ' + myescape(val) + ' ';
}
var pname = '@param' + i;
req.input('param' + i, val);
i++;
return pname;
});
req.query(sql, (err, result) => {
pool.close();
if (err) {
return cb(err);
}
result = that.__standardize(result);
cb(null, result);
});
});
}
__standardize(result) {
var length = result.recordsets.length || 1;
var set = result.recordsets[length - 1];
var columns = (set || {}).columns;
result.affectedRows = result.rowsAffected[length - 1];
if (set) {
result.rows = set;
result.metaData = (function(columns) {
var fields = [];
var col;
for ( var i in columns ) {
col = columns[i];
col.dbType = col.type.declaration;
col.varType = varTypeMap[col.dbType.toLowerCase()] || 'string';
fields.push(col);
}
return fields;
})(result.recordset.columns);
}
delete result.rowsAffected;
delete result.recordset;
return result;
}
/**
* {Function} beginTransaction
*/
beginTransaction(callback) {
var that = this;
var dbConfig = that.dbConfig;
var pool = new mssql.ConnectionPool(dbConfig);
pool.connect(err => {
if (err) {
return callback(err);
}
var transaction = pool.transaction();
transaction.begin(err => {
if (err) {
pool.close();
return callback(err);
}
// Construct connection obj.
var conn = {};
conn.rollback = function() {
transaction.rollback.apply(transaction, arguments);
};
conn.commit = function() {
transaction.commit.apply(transaction, arguments);
};
conn.release = function() {
pool.close();
};
conn.close = function() {
pool.close();
};
conn.execute = function(sql, params, cb) {
var req = transaction.request();
var i = 0;
var val;
sql = sql.replace(/\?\??/g, function(id) {
val = params[i];
if (/\?\?/.test(id)) {
i++;
return ' ' + myescapeId(val) + ' ';
}
if (val instanceof Array) {
i++;
return ' ' + myescape(val) + ' ';
}
var pname = '@param' + i;
req.input('param' + i, val);
i++;
return pname;
});
req.query(sql, (err, result) => {
if (err) {
return cb(err);
}
result = that.__standardize(result);
cb(null, result);
});
};
callback(null, conn);
});
});
}
/**
* {getConnection}
*/
getConnection(callback) {
var dbConfig = this.dbConfig;
var pool = new mssql.ConnectionPool(dbConfig);
pool.connect(err => {
if (err) {
return callback(err);
}
// Construct connection obj.
var conn = {};
conn.release = function() {
pool.close();
};
conn.close = function() {
pool.close();
};
conn.execute = function(sql, params, cb) {
var req = pool.request();
var i = 0;
var val;
sql = sql.replace(/\?\??/g, function(id) {
val = params[i];
if (/\?\?/.test(id)) {
i++;
return ' ' + myescapeId(val) + ' ';
}
// Handle array specially for batch operations
if (val instanceof Array) {
i++;
return ' ' + myescape(val) + ' ';
}
var pname = '@param' + i;
req.input('param' + i, val);
i++;
return pname;
});
req.query(sql, cb);
};
callback(null, conn);
});
}
}
module.exports = MssqlService;