sql-soar
Version:
A SQL build and query tool for node.js.
303 lines (254 loc) • 8.18 kB
JavaScript
/*!
* sql-soar
* authors: Ben Lue
* license: MIT
* License
* Copyright(c) 2018 ~ 2020 Gocharm Inc.
*/
const dbm = require('./DBManager'),
sqlComp = require('../sql/sqlComp.js'),
sqlGen = require('../sql/sqlGenMySql.js')
/**
* Execute a SQL statement which is defined in the 'expr' JSON object.
* Short-hand format:
* execute(cmd, handler)
* execute(cmd, data, handler) when 'insert' or
* execute(cmd, query, handler) for other operations
*/
exports.execute = function(cmd, data, query, handler) {
var sqlExpr = cmd.expr;
if (typeof sqlExpr === 'string') {
// new from v 1.2.0: stored expression
let dbConn,
npath = sqlExpr.split(':'), // dbName: path_to_the_SQL_expression
formulaName;
if (npath.length > 1) {
formulaName = npath[1];
dbConn = dbm.getDB( npath[0] );
}
else {
formulaName = npath[0];
dbConn = dbm.getDB();
}
sqlExpr = Object.assign({}, dbConn.getFormulaSet(formulaName));
sqlExpr.xformer = dbConn.getFormulaXFormer(formulaName);
}
else //if (sqlExpr.constructor == sqlComp)
sqlExpr = sqlExpr.value();
if (cmd.refresh)
// clean up cached data
delete sqlExpr._count;
//console.log('constructor: ' + sqlExpr.constructor);
//console.log('sqlExpr:\n' + JSON.stringify(sqlExpr, null, 4));
let nameParts = sqlExpr.table.name.trim().split('.'),
names;
if (nameParts.length > 1)
names = {
dbName: nameParts[0],
tbName: nameParts[1]
};
else
names = {
dbName: dbm.getDefaultDBName(),
tbName: nameParts[0]
};
//console.log('table names\n' + JSON.stringify(names, null, 4));
let ncmd = Object.assign( {}, cmd );
ncmd.expr = sqlExpr;
// check & auto-gen the missing parts of a SQL expression
if (!ncmd.expr.columns || !ncmd.expr.filters)
// we shall find out the columns of a table
autoFillSchema(names, ncmd, data, query, handler);
else
runTemplate(names, ncmd, data, query, handler);
}
/*
* Automatically filling missing columns or filters, and run the query
*/
function autoFillSchema(names, cmd, data, query, cb) {
if (cmd.conn)
autoFillSchemaConn(names, cmd, cmd.conn, data, query, cb);
else {
let dbName = names.dbName;
dbm.getDB(dbName).connect( (err, conn) => {
if (err) return cb(err);
autoFillSchemaConn(names, cmd, conn, data, query, (err, result, count) => {
conn.release();
if (count != undefined)
cb(err, result, count);
else
cb(err, result);
});
});
}
}
/*
* Automatically filling missing columns or filters, and run the query
*/
function autoFillSchemaConn(names, options, conn, data, query, handler) {
//console.log('expr is\n' + JSON.stringify(options.expr, null, 4));
let dbName = names.dbName,
tbName = names.tbName,
dbConn = dbm.getDB(dbName);
dbConn.getTableSchema(conn, tbName, (err, schema) => {
if (err) return handler(err);
//console.log( JSON.stringify(schema, null, 4) );
let stemp = new sqlComp(tbName), // how about 'table as alias'
columns = Object.keys(schema.columns),
op = options.op,
origExpr = options.expr;
if (origExpr.table.join)
stemp.value().table.join = origExpr.table.join;
if (origExpr.columns)
stemp.column( origExpr.columns );
else {
if (op === 'query' || op === 'list')
stemp.column( columns );
else if (data) {
// insert or update, should not put all columns in the statement
// below looks redundant, but I can't get rid of some propperties coming from nowhere
// even ownPropertyName() or hasOwnProeprty() cannot remove them
// a bug in node.js??
//data = JSON.parse(JSON.stringify(data));
var updCols = [];
for (var key in data) {
if (data.hasOwnProperty(key)) {
if (columns.indexOf(key) >= 0)
updCols.push(key);
}
}
if (updCols.length === 0)
return handler( new Error('No data for update or insertion.') );
stemp.column( updCols );
}
}
if (query) {
if (origExpr.filters)
stemp.filter( origExpr.filters );
else {
var filters = [];
for (var key in query) {
if (columns.indexOf(key) >= 0)
filters.push( {name: key, op: '='} );
}
if (filters.length > 0) {
var filter = filters.length == 1 ? filters[0] : stemp.chainFilters('AND', filters);
stemp.filter( filter );
}
}
}
stemp.extra( origExpr.extra );
let stealthExpr = stemp.value(),
cmd = {op: op, expr: stealthExpr, range: options.range, conn: conn, debug: options.debug};
stealthExpr.xformer = origExpr.xformer
runTemplate(names, cmd, data, query, function(err, result, count) {
handler(err, result, count)
})
})
}
function runTemplate(tableLoc, cmd, data, query, cb) {
if (cmd.conn)
exeCommand(tableLoc, cmd, cmd.conn, data, query, cb);
else {
let dbConn = dbm.getDB(tableLoc.dbName);
dbConn.connect( (err, conn) => {
if (err)
return cb( err );
exeCommand(tableLoc, cmd, conn, data, query, (err, value, count) => {
conn.release();
cb(err, value, count);
});
});
}
}
function exeCommand(tableLoc, options, conn, data, query, cb) {
let p = [],
expr = options.expr,
sql = getSqlGenerator().toSQL(options, data, query, p);
if (sql) {
conn.query(sql, p, function(err, value) {
let listPaging = options.op === 'list' && options.range;
if (err)
cb(err);
else {
if (options.op === 'insert')
returnInsert(conn, tableLoc, data, value, cb);
else if (listPaging) {
options.op = 'listCount';
sql = getSqlGenerator().toSQL(options, null, query, p)
conn.query(sql, p, (err, count) => {
expr._query = query
// apply transformer to the results
if (expr.xformer)
for (var i in value)
applyXformer(expr.xformer, value[i])
cb( null, value, expr._count = count && count.length ? count[0].ct : 0)
})
}
else {
if (options.op === 'query') {
value = value[0];
if (expr.xformer)
applyXformer(expr.xformer, value);
}
else if (options.op === 'list' && expr.xformer) {
for (var i in value)
applyXformer(expr.xformer, value[i]);
}
cb(null, value);
}
}
});
}
else
cb( new Error('Fail to compose the sql statement.') );
}
function returnInsert(conn, tableLoc, data, value, cb) {
let dbConn = dbm.getDB( tableLoc.dbName );
dbConn.getTableSchema(null, tableLoc.tbName, (err, schema) => {
if (err)
cb(err);
else {
let pkArray = schema.primary,
rtnObj = {};
for (let i in pkArray) {
let key = pkArray[i];
rtnObj[key] = data.hasOwnProperty(key) ? data[key] : value.insertId;
}
cb(null, rtnObj);
}
});
}
/**
* Convert data based on the transformer.
*/
function applyXformer(xformer, data) {
if (data) {
Object.keys(data).forEach( key => {
if (xformer.hasOwnProperty(key))
data[key] = xformer[key].call(this, data[key]);
});
}
}
/**
* comparing two query objects
*/
function sameQuery(q1, q2) {
//console.log('q1\n%s', JSON.stringify(q1));
//console.log('q2\n%s', JSON.stringify(q2));
var count1 = q1 ? Object.keys(q1).length : 0,
count2 = q2 ? Object.keys(q2).length : 0;
if (count1 === count2) {
if (count1) {
for (var key in q1) {
if (q1[key] !== q2[key])
return false;
}
}
return true;
}
return false;
}
function getSqlGenerator() {
return sqlGen;
}