enhancer-data-bridge
Version:
A bridge between Enhancer Clould and user business datasource
174 lines (151 loc) • 7.09 kB
JavaScript
/**
* A simple compiler to translate e-sql(the sql contains variable placeholders)
* to js-procedure.
* @Author zyz.
* @updated: 2021/03/30
*/
// SQL result regex.
var rReg = /\$\d+((\.\w+)|(\[\s*\d+\s*\])|(\[\s*'[^']+'\s*\])|(\[\s*"[^"]+"\s*\]))+/;
// SQL identifier regex.
var iReg = /\$(\d+-)?\w+(\.\w+)*\$/;
// SQL parameter regex.
var pReg = /\@(\d+-)?\w+(\.\w+)*\@/;
// All types of placeholder regex
var reg = /(\$(\d+-)?\w+(\.\w+)*\$)|(\@(\d+-)?\w+(\.\w+)*\@)|(\$\d+((\.\w+)|(\[\s*\d+\s*\])|(\[\s*'[^']+'\s*\])|(\[\s*"[^"]+"\s*\]))+)/g;
var expReg = /\#([^\#])*\#/g;
// Identifiers and parameters
var ipReg = /(\@(\d+-)?\w+(\.\w+)*\@)|(\$(\d+-)?\w+(\.\w+)*\$)/g;
var requireNocache = require('require-nocache')(module);
var procedureTemplateTransaction = require('./procedure-template-transaction.js');
var procedureTemplate = require('./procedure-template.js');
var os = require('os');
var path = require('path');
var customModuleBase = path.resolve(__dirname, '../repository/project/custom-module');
if (os.platform() === 'win32') {
customModuleBase = customModuleBase.replace(/\\/g, '/') + '/';
} else {
customModuleBase = customModuleBase + '/';
}
var compiler = {
compile: function(eSql) {
// Remove blank.
eSql = eSql.replace(/(^\s+)|(\s+$)/g, '');
// Remove comments.
eSql = eSql.replace(/\/\*(.|\s)+?\*\//g, '')
.replace(/--[^'\n]*('[^'\n]*'[^'\n]*)+/g, '')
.replace(/--[^'\n]+/g, '');
eSql = eSql.replace(expReg, function(s) {
return s.replace(/\@/g, '{^}')
.replace(/\$/g, '{&}')
.replace(/\;/g, '{%}');
});
if (!eSql) {
return function (connectionName, parameters, serverVariables, Enhancer, done) {done(null, {success: true, results: []})};
}
var series = false;
var sideEffectsSQLCount = 0;
var esqlSet = eSql.split(/;/g)
.map(function(sql) {
return sql.replace(/^\s|\s$/g, '');
})
.filter(function(sql) {
if (sql && !/^SELECT\s/i.test(sql)) {
sideEffectsSQLCount++;
}
return sql ? true : false;
});
var fragments = esqlSet.map(function(sql, index) {
sql = sql.trim();
if (!sql) {
return '';
}
var fragment = [];
var params = [];
var dependencies = [];
sql = sql.replace(reg, function(s) {
return '#' + s + '#';
})
.replace(/\{\^\}/g, '@')
.replace(/\{\&\}/g, '$')
.replace(/\{\%\}/g, ';');
sql = sql
.replace(expReg, function(s) {
if (rReg.test(s)) {
series = true;
}
var exp = s.replace(/\#/g, '').replace(/(^\s*)|(\s*$)/g, '');
// custom module
exp = exp.replace(/require\(\s*(\'|\")@custom\//g, function(s, $1) {
return 'require(' + $1 + customModuleBase;
});
exp = exp.replace(ipReg, function(ss) {
var name = ss.split(/\$|\@/)[1];
if (/^\w+(\.\w+)*$/.test(name)) {
return ' Enhancer.getVariable(\'' + name.toUpperCase() + '\')';
} else {
return ' parameters[\'' + name.toUpperCase() + '\']';
}
});
try {
new Function(exp);
} catch(ex) {
ex.message = 'Invalid parameter or JavaScript expression: ' + s
+ '. SQL index: ' + index + '. Reason: ' + ex.message;
throw ex;
}
var p = '(function(){try{return ' + exp
+ '}catch(ex){ex.message += `. Javascript Expression: ' + s + '`; throw ex}})()';
// Check syntax
if (iReg.test(s)) {
return '{&}\n+ escapeId(' + p + ').replace(/`/g, \'\') +\n{&}'
}
params.push(p);
return '?';
})
.replace(/\n\s*/g, ' ');
sql = '"' + sql.replace(/"/g, '\\"') + '"';
sql = sql.replace(/\{\&\}/g, '"');
var TAB = ' ';
fragment.push('/********');
fragment.push(' * SQL ' + index);
fragment.push(' ********/');
fragment.push('transaction.push(function(callback) {');
fragment.push(' var sql');
fragment.push(' var params')
fragment.push(' try {')
fragment.push(' sql = ' + sql);
fragment.push(' params = [' + params.join(',\n ') + ']');
fragment.push(' } catch (ex) {');
fragment.push(' ex.message = "Invalid Javascript expression. Caused by: " + ex.message');
fragment.push(' ex.debugInfo = {sqlIndex: ' + index + ', sql: sql, param: params}');
fragment.push(' return callback(ex)');
fragment.push(' }');
fragment.push(' logger.debug("SQL ' + index + ': " , "\\n", sql)');
fragment.push(' params.length && params[0] instanceof Array && params[0].length > 1000 ? logger.warn("PARAMS SIZE IS TOO LARGE TO PRINT.") : logger.debug("PARAMS: " , "\\n", JSON.stringify(params))');
fragment.push(' if (params.length === 1 && params[0] instanceof Array && !params[0].length) {return callback(null, {rows: [],insertId: null,affectedRows: 0});}');
fragment.push(' databaseConnection.execute(sql, params, function(err, result) {');
fragment.push(' if (err) {\n');
fragment.push(' err.debugInfo = {sqlIndex: ' + index + ', sql: sql, params: params, originErrorMessage: err.message}');
fragment.push(' err.message += " SQL index: ' + index + '"');
fragment.push(' return callback(err)');
fragment.push(' }');
fragment.push(' result.rows && result.rows.length > 1000 ? logger.warn("RESULT SIZE IS TOO LARGE TO PRINT") : logger.debug("RESULT: " , "\\n", result)');
fragment.push(' $' + index + ' = result');
fragment.push(' callback(null, result)');
fragment.push(' })');
fragment.push('})');
return TAB + fragment.join('\n' + TAB);
});
var funcStr;
var isTransaction = sideEffectsSQLCount > 1;
if (isTransaction) {
funcStr = procedureTemplateTransaction
.replace('{{TRANSACTION-BLOCKS}}', fragments.join('\n\n'));
} else {
funcStr = procedureTemplate.replace('{{TRANSACTION-BLOCKS}}', fragments.join('\n\n'))
.replace('{{SERIES}}', series ? 'series' : 'parallel');
}
return eval( funcStr );
}
};
module.exports = compiler;