total4
Version:
Total.js framework v4
333 lines (300 loc) • 9.4 kB
JavaScript
require('./index');
// Total.js Module: NoSQL embedded
const REG_NULLABLE = /\./g;
const REG_LANGUAGE = /[a-z0-9]+§/gi;
const CANSTATS = global.F ? (global.F.stats && global.F.stats.performance && global.F.stats.performance.dbrm != null) : false;
const LOGGER = '-- NoSQL -->';
const FILTER = { find: 1, read: 1, count: 1, scalar: 1, check: 1, list: 1, update: 1, remove: 1, query: 1 };
const Open = {};
function db_where(where, opt, filter, operator, args) {
var tmp;
for (var item of filter) {
if (item.comparer) {
switch (item.comparer) {
case '=':
item.comparer = '==';
break;
case '<>':
item.comparer = '!=';
break;
}
}
if (opt.language != null && item.name && item.name[item.name.length - 1] === '§')
item.name = item.name.substring(0, item.name.length - 1) + opt.language;
switch (item.type) {
case 'or':
tmp = [];
db_where(tmp, opt, item.value, '||', args);
where.length && where.push(operator);
where.push('(' + tmp.join(' ') + ')');
break;
case 'in':
case 'notin':
where.length && where.push(operator);
where.push((item.type === 'notin' ? '!' : '') + 'func.in(doc.' + item.name.replace(REG_NULLABLE, '?.') + ',arg.params[' + args(item.value) + '])');
break;
case 'query':
where.length && where.push(operator);
where.push('(' + item.value + ')');
break;
case 'where':
where.length && where.push(operator);
where.push('doc.' + item.name.replace(REG_NULLABLE, '?.') + item.comparer + 'arg.params[' + args(item.value) + ']');
break;
case 'contains':
where.length && where.push(operator);
where.push('(doc.{0} instanceof Array?!!doc.{0}.length:!!doc.{0})'.format(item.name.replace(REG_NULLABLE, '?.')));
break;
case 'search':
var paramindex = args(item.value.replace(/%/g, ''));
where.length && where.push(operator);
where.push('func.search(doc.' + item.name.replace(REG_NULLABLE, '?.') + ',arg.params[' + paramindex + ']' + (item.comparer == 'beg' ? ',1' : item.comparer == 'end' ? ',2' : '') + ')');
break;
case 'month':
case 'year':
case 'day':
case 'hour':
case 'minute':
where.length && where.push(operator);
where.push(compare_datetype(item.type, item.name, args(item.value), item.comparer));
break;
case 'empty':
where.length && where.push(operator);
where.push('(doc.{0} instanceof Array?!doc.{0}.length:!doc.{0})'.format(item.name.replace(REG_NULLABLE, '?.')));
break;
case 'between':
var ia = args(item.a);
var ib = args(item.b);
where.length && where.push(operator);
where.push('(doc.' + item.name.replace(REG_NULLABLE, '?.') + '>=arg.params[' + ia + ']&&doc.' + item.name + '<=arg.params[' + ib + '])');
break;
}
}
}
function db_insertupdate(filter, insert) {
var query = insert ? null : [];
var params = {};
for (var key in filter.payload) {
var val = filter.payload[key];
var c = key[0];
switch (c) {
case '-':
case '+':
case '*':
case '/':
key = key.substring(1);
params[key] = val ? val : 0;
if (!insert)
query.push('doc.{0}=(doc.{0}==null?0:doc.{0})+arg.{0}'.format(key));
break;
case '>':
case '<':
key = key.substring(1);
params[key] = val ? val : 0;
if (!insert)
query.push('doc.{0}=(doc.{0}==null?arg.{0}:doc.{0}' + (c === '>' ? '<' : '>') + 'arg.{0}?arg.{0}:doc.{0})'.format(key));
break;
case '!':
// toggle
key = key.substring(1);
if (insert)
params[key] = true;
else
query.push('doc.{0}=!doc.{0}'.format(key));
break;
case '=':
case '#':
// raw
key = key.substring(1);
if (insert)
params[key] = val;
else
query.push('doc.' + key + '=' + val);
break;
default:
params[key] = val;
if (!insert)
query.push('doc.{0}=arg.{0}'.format(key));
break;
}
}
return { query, params };
}
function makefilter(db, opt, callback) {
var where = [];
var model = {};
var isread = false;
var args = [];
var exec = opt.exec;
db_where(where, opt, opt.filter, '&&', val => args.push(val) - 1);
var builder = {};
var insert = exec === 'insert';
if (insert || exec === 'update') {
var iu = db_insertupdate(opt, insert);
if (iu) {
if (insert) {
builder.payload = iu.params;
} else {
builder.modify = iu.query ? iu.query.join(';') : null;
builder.modifyarg = iu.params;
}
}
}
if (FILTER[exec]) {
builder.filter = where.join('') || 'true';
builder.filterarg = { params: args };
builder.take = opt.take;
builder.skip = opt.skip;
builder.first = opt.first;
if (opt.sort && opt.sort.length) {
builder.sort = opt.sort.join(',');
if (opt.language != null)
builder.sort = builder.sort.replace(REG_LANGUAGE, val => (val.substring(0, val.length - 1) + opt.language));
}
}
if (opt.fields) {
builder.fields = opt.fields.join(',');
if (opt.language != null && builder.fields) {
builder.fields = builder.fields.replace(REG_LANGUAGE, function(val) {
val = val.substring(0, val.length - 1);
return val + (opt.language ? (opt.language + ' as ' + val) : '');
});
}
}
switch (exec) {
case 'find':
isread = true;
db.find().assign(builder).$callback = callback;
break;
case 'count':
isread = true;
builder.scalar = 'arg.count+=1';
builder.scalararg = { count: 0 };
db.find().assign(builder).$callback = callback;
break;
case 'check':
builder.first = true;
isread = true;
db.find().assign(builder).$callback = (err, response) => callback(err, !!response);
break;
case 'list':
db.find().assign(builder).$callback = (err, response, meta) => callback(err, err ? null : { items: response, count: meta.count });
isread = true;
break;
case 'read':
db.find2().assign(builder).$callback = callback;
isread = true;
break;
case 'insert':
db.insert().assign(builder).$callback = callback;
break;
case 'update':
db.update().assign(builder).$callback = callback;
break;
case 'remove':
db.remove().assign(builder).$callback = callback;
break;
case 'truncate':
db.clear(callback);
break;
case 'drop':
db.drop(callback);
delete Open[db.$qbkey];
break;
case 'scalar':
switch (opt.scalar.type) {
case 'avg':
case 'min':
case 'sum':
case 'max':
case 'count':
if (opt.scalar.key2) {
builder.scalar = 'var k=doc.' + opt.scalar.key + '+\'\';if (arg[k]){tmp.bk=doc.' + opt.scalar.key2 + '||0;' + (opt.scalar.type === 'max' ? 'if(tmp.bk>arg[k])arg[k]=tmp.bk' : opt.scalar.type === 'min' ? 'if(tmp.bk<arg[k])arg[k]=tmp.bk' : 'arg[k]+=tmp.bk') + '}else{arg[k]=doc.' + opt.scalar.key2 + '||0}';
} else {
builder.scalar = 'if (doc.{0}!=null){tmp.val=doc.{0};arg.count+=1;arg.min=arg.min==null?tmp.val:arg.min>tmp.val?tmp.val:arg.min;arg.max=arg.max==null?tmp.val:arg.max<tmp.val?tmp.val:arg.max;if(!(tmp.val instanceof Date))arg.sum+=tmp.val}'.format(opt.scalar.key);
builder.scalararg = { count: 0, sum: 0 };
}
db.find().assign(builder).$callback = function(err, response) {
var output = {};
if (response)
output.value = opt.scalar.type === 'avg' ? ((response.min + response.max) / 2) : response[opt.scalar.type];
callback(err, output);
};
break;
case 'group':
builder.scalar = opt.scalar.key2 ? 'if (doc.{0}!=null){tmp.val=doc.{0};arg[tmp.val]=(arg[tmp.val]||0)+(doc.{1}||0)}'.format(opt.scalar.key, opt.scalar.key2) : 'if (doc.{0}!=null){tmp.val=doc.{0};arg[tmp.val]=(arg[tmp.val]||0)+1}'.format(opt.scalar.key);
builder.scalararg = {};
db.find().assign(builder).$callback = function(err, response) {
var output = [];
for (var key in response) {
var val = response[key];
var mod = {};
mod[opt.scalar.key] = key;
mod.value = val;
output.push(mod);
}
callback(err, output);
};
break;
}
isread = true;
break;
}
if (opt.debug)
console.log(LOGGER, db.filename, exec, builder);
if (CANSTATS) {
if (isread)
F.stats.performance.dbrm++;
else
F.stats.performance.dbwm++;
}
return model;
}
function compare_datetype(type, key, paramindex, operator) {
switch (operator) {
case '=':
operator = '==';
break;
case '<>':
operator = '!=';
break;
}
switch (type) {
case 'day':
type = 'getDate()';
break;
case 'month':
type = 'getMonth()+1';
break;
case 'year':
type = 'getFullYear()';
break;
case 'hour':
type = 'getHour()';
break;
case 'minute':
type = 'getMinute()';
break;
}
return 'doc.{0}&&doc.{0}.getTime?doc.{0}.{3}{2}arg.params[{1}]:false'.format(key.replace(REG_NULLABLE, '?.'), paramindex, operator, type);
}
NEWDB('nosql', function($, next) {
var type = 'nosql';
var key = type + '_' + $.table;
if (!Open[key]) {
var filename = $.table[0] === '~' ? $.table.substring(1) : PATH.databases($.table + '.' + type);
Open[key] = require('./textdb').JsonDB(filename, false);
Open[key].$qbkey = key;
}
makefilter(Open[key], $, next);
});
NEWDB('inmemory', function($, next) {
var type = 'inmemory';
var key = type + '_' + $.table;
if (!Open[key]) {
var filename = $.table[0] === '~' ? $.table.substring(1) : PATH.databases($.table + '.' + type);
Open[key] = require('./inmemory').load(filename);
Open[key].$qbkey = key;
}
makefilter(Open[key], $, next);
});