icorm
Version:
Database Management System
788 lines (672 loc) • 20.1 kB
JavaScript
const MongoDB = require('mongodb');
const MongoClient = MongoDB.MongoClient;
const EMPTYARRAY = [];
const INSERTPROJECTION = { projection: { '_id': 1 }};
const BUCKETNAME = { bucketName: 'db' };
const BLACKLIST = { dbms: 1 };
global.ObjectID = MongoDB.ObjectID;
function select(client, cmd) {
var builder = cmd.builder;
var opt = builder.options;
var filter = WHERE(builder);
var options = {};
var fields = FIELDS(builder);
if (fields)
options.projection = fields;
if (opt.take)
options.limit = opt.take;
if (opt.skip)
options.skip = opt.skip;
if (filter.sort)
options.sort = filter.sort;
builder.db.$debug && builder.db.$debug({ collection: client.$database + '.' + opt.table, condition: filter.where, options: options });
client.db(client.$database).collection(opt.table).find(filter.where, options).toArray(function(err, response) {
client.close();
err && client.$opt.onerror && client.$opt.onerror(err, opt, builder);
var rows = response ? response : EMPTYARRAY;
if (opt.first)
rows = rows.length ? rows[0] : null;
// checks joins
if (!err && builder.$joins) {
client.$dbms._join(rows, builder);
setImmediate(builder.db.$next);
} else
builder.$callback(err, rows);
});
}
function bigselect(client, cmd) {
var builder = cmd.builder;
var opt = builder.options;
var filter = WHERE(builder);
var options = {};
var fields = FIELDS(builder);
if (fields)
options.projection = fields;
if (opt.take)
options.limit = opt.take;
if (opt.skip)
options.skip = opt.skip;
if (!filter.sort)
filter.sort = {"_id": 1};
builder.db.$debug && builder.db.$debug({ collection: client.$database + '.' + opt.table, condition: filter.where, options: options });
client.db(client.$database).collection(opt.table).find(filter.where, options).sort(filter.sort).allowDiskUse().toArray(function(err, response) {
client.close();
err && client.$opt.onerror && client.$opt.onerror(err, opt, builder);
var rows = response ? response : EMPTYARRAY;
if (opt.first)
rows = rows.length ? rows[0] : null;
// checks joins
if (!err && builder.$joins) {
client.$dbms._join(rows, builder);
setImmediate(builder.db.$next);
} else
builder.$callback(err, rows);
});
}
function query(client, cmd) {
var builder = cmd.builder;
var opt = builder.options;
var filter = WHERE(builder);
var options = {};
var fields = FIELDS(builder);
if (fields)
options.projection = fields;
if (opt.take)
options.limit = opt.take;
if (opt.skip)
options.skip = opt.skip;
if (filter.sort)
options.sort = filter.sort;
var col = client.db(client.$database).collection(cmd.query);
cmd.value(col, function(err, response) {
if (opt.first && response instanceof Array)
response = response[0];
client.close();
err && client.$opt.onerror && client.$opt.onerror(err, opt, builder);
builder.$callback(err, response);
}, filter.where, options);
}
function listCollections(client, cmd) {
var builder = cmd.builder;
var opt = builder.options;
client.db(client.$database).listCollections().toArray(function(err, response) {
client.close();
err && client.$opt.onerror && client.$opt.onerror(err, opt, builder);
builder.$callback(err, response);
});
}
async function listDatabases(client, cmd) {
var builder = cmd.builder;
var opt = builder.options;
var result = await client.db(client.$database).admin().listDatabases();
client.close();
builder.$callback(result);
}
function pipeLines(client, cmd) {
var builder = cmd.builder;
var opt = builder.options;
var filter = WHERE(builder, true);
client.db(client.$database).collection(opt.table).aggregate([ filter, cmd.improved ]).toArray(function(err, response) {
err && client.$opt.onerror && client.$opt.onerror(err, opt, builder);
client.close();
builder.$callback(err, response);
});
}
function list(client, cmd) {
var builder = cmd.builder;
var opt = builder.options;
var filter = WHERE(builder);
var options = {};
var fields = FIELDS(builder);
if (fields)
options.projection = fields;
if (opt.take)
options.limit = opt.take;
if (opt.skip)
options.skip = opt.skip;
if (filter.sort)
options.sort = filter.sort;
builder.db.$debug && builder.db.$debug({ collection: client.$database + '.' + opt.table, condition: filter.where, options: options });
var db = client.db(client.$database).collection(opt.table);
db.countDocuments(filter.where, function(err, count) {
if (err) {
client.close();
err && client.$opt.onerror && client.$opt.onerror(err, opt, builder);
builder.$callback(err, null);
} else {
db.find(filter.where, options).toArray(function(err, response) {
client.close();
if (!err && builder.$joins) {
client.$dbms._joins(response, builder, count || 0);
setImmediate(builder.db.$next);
} else
builder.$callback(err, response, count);
});
}
});
}
function scalar(client, cmd) {
var builder = cmd.builder;
var opt = builder.options;
var filter = WHERE(builder, true);
var cmdgroup = '$' + cmd.name;
// builder.db.$debug && builder.db.$debug(q);
switch (cmd.scalar) {
case 'count':
client.db(client.$database).collection(opt.table).aggregate([ filter, {$count: 'count'} ]).toArray(function(err, response) {
err && client.$opt.onerror && client.$opt.onerror(err, opt, builder);
client.close();
var rescount = 0;
if(response.length)
rescount = response[0].count;
builder.$callback(err, rescount);
});
break;
case 'avg':
client.db(client.$database).collection(opt.table).aggregate([ filter, {$group: { _id: '', average:{ $avg: cmdgroup }}} ]).toArray(function(err, response) {
err && client.$opt.onerror && client.$opt.onerror(err, opt, builder);
client.close();
builder.$callback(err, response[0].average);
});
break;
case 'min':
client.db(client.$database).collection(opt.table).aggregate([ filter, {$group: { _id: '', min:{ $min: cmdgroup }}} ]).toArray(function(err, response) {
err && client.$opt.onerror && client.$opt.onerror(err, opt, builder);
client.close();
builder.$callback(err, response[0].min);
});
break;
case 'max':
client.db(client.$database).collection(opt.table).aggregate([ filter, {$group: { _id: '', max:{ $max: cmdgroup }}} ]).toArray(function(err, response) {
err && client.$opt.onerror && client.$opt.onerror(err, opt, builder);
client.close();
builder.$callback(err, response[0].max);
});
break;
case 'sum':
client.db(client.$database).collection(opt.table).aggregate([ filter, { $group:{ _id: '', sum:{ $sum: cmdgroup }}} ]).toArray(function(err, response) {
err && client.$opt.onerror && client.$opt.onerror(err, opt, builder);
client.close();
builder.$callback(err, response[0].sum);
});
break;
case 'group':
client.db(client.$database).collection(opt.table).aggregate([ filter, { $count: 'count' } ]).toArray(function(error, count) {
error && client.$opt.onerror && client.$opt.onerror(error, opt, builder);
client.db(client.$database).collection(opt.table).aggregate([ filter, { $group:{ _id: cmdgroup, count:{ $sum:1 }}}, { $sort: { count: -1 } }]).toArray(function(err, response) {
err && client.$opt.onerror && client.$opt.onerror(err, opt, builder);
client.close();
var obj = {};
if(count && count[0] && count[0].count)
obj.total = count[0].count;
else
obj.total = 0;
obj.items = response;
builder.$callback(err, obj);
});
});
break;
}
}
async function insert(client, cmd) {
var builder = cmd.builder;
var keys = Object.keys(cmd.builder.value);
var opt = builder.options;
var params = {};
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var val = cmd.builder.value[key];
if (val === undefined || BLACKLIST[key])
continue;
if (cmd.builder.options.fields && cmd.builder.options.fields.length) {
var skip = true;
for (var j = 0; j < cmd.builder.options.fields.length; j++) {
if (cmd.builder.options.fields[j] == key || cmd.builder.options.fields[j] == key.substring(1)) {
skip = false;
break;
}
}
if (skip)
continue;
}
switch (key[0]) {
case '&':
key = key.substring(1);
var gencode = val;
gencode.key = key;
break;
case '%':
key = key.substring(1);
var gensearch = val;
gensearch.key = key;
break;
case '-':
case '+':
case '*':
case '/':
key = key.substring(1);
break;
case '<':
case '>':
key = key.substring(1);
break;
}
params[key] = val == null ? null : typeof(val) === 'function' ? val(cmd.builder.value) : val;
}
if(gencode) {
var db = DBMS();
var codegen = await db.one(gencode.indexes).where('code', gencode.precode).promise();
db.modify(gencode.indexes, { '+index': +1 } ).where('code', gencode.precode);
var indexcode = codegen.index + 1;
indexcode = indexcode.toString();
while (indexcode.length < gencode.output) {
indexcode = '0' + indexcode;
}
params[gencode.key] = gencode.precode + indexcode;
if(gensearch) {
var one = gensearch.one;
var two = gensearch.two;
params[gensearch.key] = params[one] + gensearch.join + params[two];
}
}
builder.db.$debug && builder.db.$debug();
client.db(client.$database).collection(opt.table).insertOne(params, function(err, response) {
client.close();
err && client.$opt.onerror && client.$opt.onerror(err, opt, builder);
builder.$callback(err, response && response.result && response.result.n ? 1 : 0, params);
});
}
function insertexists(client, cmd) {
var builder = cmd.builder;
var opt = builder.options;
var filter = WHERE(builder);
// builder.db.$debug && builder.db.$debug(q);
client.db(client.$database).collection(opt.table).findOne(filter.where, INSERTPROJECTION, function(err, response) {
if (err || response) {
client.close();
err && client.$opt.onerror && client.$opt.onerror(err, opt, builder);
builder.$callback(err, 0);
} else
insert(client, cmd);
});
}
function modify(client, cmd) {
cmd.builder.options.transform && cmd.builder.options.transform(cmd.builder.value, cmd.builder.db.$output, cmd.builder.db.$lastoutput);
var keys = Object.keys(cmd.builder.value);
var params = null;
var increment = null;
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var val = cmd.builder.value[key];
if (val === undefined || BLACKLIST[key])
continue;
if (cmd.builder.options.fields && cmd.builder.options.fields.length) {
var skip = true;
for (var j = 0; j < cmd.builder.options.fields.length; j++) {
if (cmd.builder.options.fields[j] == key || cmd.builder.options.fields[j] == key.substring(1)) {
skip = false;
break;
}
}
if (skip)
continue;
}
var c = key[0];
if (typeof(val) === 'function')
val = val(cmd.builder.value);
switch (c) {
case '-':
case '+':
case '*':
case '/':
!increment && (increment = {});
key = key.substring(1);
increment[key] = val ? val : 0;
break;
case '<':
case '>':
key = key.substring(1);
break;
default:
!params && (params = {});
params[key] = val;
break;
}
}
var builder = cmd.builder;
var opt = builder.options;
var filter = WHERE(builder);
// builder.db.$debug && builder.db.$debug(q);
var upd = {};
increment && (upd.$inc = increment);
params && (upd.$set = params);
var col = client.db(client.$database).collection(opt.table);
var callback = function(err, response) {
err && client.$opt.onerror && client.$opt.onerror(err, opt, builder);
var count = response && response.modifiedCount ? response.modifiedCount : 0;
if (!count && cmd.insert) {
if (cmd.insert !== true)
cmd.builder.value = cmd.insert;
cmd.builder.options.insert && cmd.builder.options.insert(cmd.builder.value, cmd.builder.options.insertparams);
insert(client, cmd);
} else {
client.close();
builder.$callback(err, count);
}
};
if (opt.first)
col.updateOne(filter.where, upd, callback);
else
col.updateMany(filter.where, upd, callback);
}
function remove(client, cmd) {
var builder = cmd.builder;
var opt = builder.options;
var filter = WHERE(builder);
// builder.db.$debug && builder.db.$debug(q);
var col = client.db(client.$database).collection(opt.table);
var callback = function(err, response) {
client.close();
err && client.$opt.onerror && client.$opt.onerror(err, opt, builder);
builder.$callback(err, response && response.result ? response.result.n : 0);
};
if (opt.first)
col.deleteOne(filter.where, callback);
else
col.deleteMany(filter.where, callback);
}
exports.run = function(opt, self, cmd) {
var client = new MongoClient(opt.options);
client.connect(function(err) {
client.$opt = opt;
client.$dbms = self;
client.$database = opt.database;
if (err) {
opt.onerror && client.$opt.onerror(err);
if (cmd.builder)
cmd.builder.$callback(err);
else
cmd.db.$next(err);
} else {
switch (cmd.type) {
case 'transaction':
case 'commit':
case 'rollback':
cmd.$next(new Error('"' + cmd.type + '" is not implemented.'));
break;
case 'bigFind':
bigselect(client, cmd);
break;
case 'find':
case 'read':
select(client, cmd);
break;
case 'diff':
var cb = cmd.builder.$callback;
cmd.builder.$callback = function(err, response) {
cb.call(cmd.builder, err, err ? EMPTYOBJECT : DIFFARR(cmd.key, response, [cmd.form]));
};
select(client, cmd);
break;
case 'list':
list(client, cmd);
break;
case 'listCollections':
listCollections(client, cmd);
break;
case 'listDatabases':
listDatabases(client, cmd);
break;
case 'pipeLines':
pipeLines(client, cmd);
break;
case 'scalar':
scalar(client, cmd);
break;
case 'insert':
if (cmd.unique)
insertexists(client, cmd);
else
insert(client, cmd);
break;
case 'update':
case 'modify':
modify(client, cmd);
break;
case 'remove':
remove(client, cmd);
break;
case 'query':
query(client, cmd);
break;
default:
client.close();
cmd.builder.$callback(new Error('Operation "' + cmd.type + '" not found'));
break;
}
}
});
};
exports.blob_read = function(opt, id, callback, conn) {
var client = new MongoClient(opt.options);
client.connect(function(err) {
if (err) {
callback(err);
return;
}
if (conn.table && conn.table !== 'default')
BUCKETNAME.bucketName = conn.table;
else
BUCKETNAME.bucketName = 'db';
var done = () => client.close();
var bucket = new MongoDB.GridFSBucket(client.db(opt.database), BUCKETNAME);
var stream = bucket.openDownloadStream(id);
stream.on('error', done);
stream.on('end', done);
callback(null, stream);
});
};
exports.blob_write = function(opt, stream, name, callback, conn) {
var client = new MongoClient(opt.options);
client.connect(function(err) {
if (err) {
callback(err);
return;
}
if (conn.table && conn.table !== 'default')
BUCKETNAME.bucketName = conn.table;
else
BUCKETNAME.bucketName = 'db';
var options = global.U ? { contentType: U.getContentType(U.getExtension(name)) } : null;
var bucket = new MongoDB.GridFSBucket(client.db(opt.database), BUCKETNAME);
var writer = bucket.openUploadStream(name, options);
stream.pipe(writer).on('error', function(err) {
client.close();
callback(err);
}).on('finish', function() {
client.close();
callback(null, writer.id);
});
});
};
exports.blob_remove = function(opt, id, callback, conn) {
var client = new MongoClient(opt.options);
client.connect(function(err) {
if (err) {
callback && callback(err);
return;
}
if (conn.table && conn.table !== 'default')
BUCKETNAME.bucketName = conn.table;
else
BUCKETNAME.bucketName = 'db';
var bucket = new MongoDB.GridFSBucket(client.db(opt.database), BUCKETNAME);
bucket.delete(id, function(err) {
client.close();
callback && callback(err);
});
});
};
function eqgtlt(cmd) {
switch (cmd.compare) {
case '>':
return { $gt: cmd.value };
case '>=':
return { $gte: cmd.value };
case '<':
return { $lt: cmd.value };
case '<=':
return { $lte: cmd.value };
}
}
function WHERE(builder, scalar) { // , group
var condition = {};
var sort = null;
var tmp = null;
var filter;
var value;
for (var i = 0; i < builder.$commands.length; i++) {
var cmd = builder.$commands[i];
switch (cmd.type) {
case 'where':
value = cmd.compare === '=' ? cmd.value : eqgtlt(cmd);
if (tmp) {
filter = {};
filter[cmd.name] = value;
tmp.push(filter);
} else
condition[cmd.name] = value;
break;
case 'in':
if (typeof(cmd.value) === 'function')
cmd.value = cmd.value();
value = cmd.value instanceof Array ? { $in: cmd.value } : cmd.value;
if (tmp) {
filter = {};
filter[cmd.name] = value;
tmp.push(filter);
} else
condition[cmd.name] = value;
break;
case 'notin':
if (typeof(cmd.value) === 'function')
cmd.value = cmd.value();
value = cmd.value instanceof Array ? { $nin: cmd.value } : { $ne: cmd.value };
if (tmp) {
filter = {};
filter[cmd.name] = value;
tmp.push(filter);
} else
condition[cmd.name] = value;
break;
case 'between':
value = { $gte: cmd.a, $lte: cmd.b };
if (tmp) {
filter = {};
filter[cmd.name] = value;
tmp.push(filter);
} else
condition[cmd.name] = value;
break;
case 'search':
value = (cmd.compare === '*' ? '' : cmd.compare === 'beg' ? '^' : '') + cmd.value + (cmd.compare === 'end' ? '$' : '');
var v = ['[aáä]', '[eéë]', '[iíï]', '[oóö]', '[uúü]'];
for (let i = 0; i < v.length; i++)
value = value.replace(new RegExp(v[i], 'gi'), v[i]);
value = { $regex: value, $options: 'i' };
if (tmp) {
filter = {};
filter[cmd.name] = value;
tmp.push(filter);
} else
condition[cmd.name] = value;
break;
case 'fulltext':
value = new RegExp((cmd.compare === '*' ? '' : cmd.compare === 'beg' ? '^' : '') + cmd.value + (cmd.compare === 'end' ? '$' : ''), 'i');
if (tmp) {
filter = {};
filter[cmd.name] = value;
tmp.push(filter);
} else
condition[cmd.name] = value;
break;
case 'contains':
value = { $exists: true, $nin: [null, ''] };
if (tmp) {
filter = {};
filter[cmd.name] = value;
tmp.push(filter);
} else
condition[cmd.name] = value;
break;
case 'empty':
value = { $exists: false, $in: [null, ''] };
if (tmp) {
filter = {};
filter[cmd.name] = value;
tmp.push(filter);
} else
condition[cmd.name] = value;
break;
case 'month':
case 'year':
case 'day':
case 'hour':
case 'minute':
new Error('Not implemented', 'MongoDB doesn\'t support date function for quering.');
break;
case 'query':
if (tmp) {
filter = {};
tmp.push(cmd.query);
} else {
var arr = Object.keys(cmd.query);
for (var j = 0; j < arr.length; j++)
condition[arr[j]] = cmd.query[arr[j]];
}
break;
case 'or':
tmp = [];
continue;
case 'end':
condition.$or = tmp;
tmp = null;
break;
case 'and':
break;
case 'sort':
!sort && (sort = {});
sort[cmd.name] = cmd.desc ? -1 : 1;
break;
case 'regexp':
if (tmp) {
filter = {};
filter[cmd.name] = cmd.value;
tmp.push(filter);
} else
condition[cmd.name] = cmd.value;
break;
}
}
if (scalar)
return { $match: condition };
else
return { where: condition, sort: sort };
}
function FIELDS(builder) {
var output = null;
var fields = builder.options.fields;
if (fields && fields.length) {
output = {};
for (var i = 0; i < fields.length; i++) {
var name = fields[i];
var is = name[0] === '-';
if (is)
name = name.substring(1);
output[name] = is ? 0 : 1;
}
if (builder.$joinmeta)
output[builder.$joinmeta.a] = 1;
}
return output;
}