UNPKG

@teamnet/ic-orm

Version:

Database Management System

2,171 lines (1,795 loc) 73.3 kB
const Fs = require('fs'); const Url = require('url'); const Qs = require('querystring'); const CONN = {}; const CACHE = {}; /** * Elimina un parámetro de una URL * @param {string} url - URL de conexión * @param {string} paramName - Nombre del parámetro a eliminar * @returns {string} - URL sin el parámetro especificado */ function removeParamFromURL(url, paramName) { var parsedUrl = Url.parse(url, true); if (parsedUrl.search) { // Eliminar el parámetro del objeto query delete parsedUrl.query[paramName]; // Reconstruir la cadena de consulta parsedUrl.search = '?' + Qs.stringify(parsedUrl.query); } // Formato estándar: eliminar la propiedad search para que se use query al formatear delete parsedUrl.search; // Reconstruir la URL return Url.format(parsedUrl); } const COMPARE = { '<': '<', '>': '>', '>=': '>=', '=>': '>=', '=<': '<=', '<=': '<=', '==': '=', '===': '=', '!=': '!=', '<>': '!=', '=': '=' }; const MODIFY = { insert: 1, update: 1, modify: 1 }; const TEMPLATES = {}; const REG_FIELDS_CLEANER = /"|`|\||'|\s/g; const CACHEBLACKLIST = { insert: 1, modify: 1, update: 1, remove: 1 }; // A temporary cache for fields (it's cleaning each 10 minutes) var FIELDS = {}; var auditwriter; function promise(fn) { var self = this; var $; if (fn && typeof(fn) === 'object') { $ = fn; fn = null; } return new Promise(function(resolve, reject) { self.callback(function(err, result) { if (err) { if ($) $.invalid(err); else reject(err); } else resolve(fn ? fn(result) : result); }); }); } var logger; function DBMS(errbuilder) { var self = this; self.$conn = {}; self.$commands = []; self.$output = {}; self.response = self.$outputall = {}; self.$eb = global.ErrorBuilder != null; self.$errors = errbuilder || (global.ErrorBuilder ? new global.ErrorBuilder() : []); // self.$log; // self.$lastoutput; self.$next = function(err) { err && self.$errors.push(err); self.next(); }; } const DP = DBMS.prototype; DP.promise = promise; DP.cache = function(key, expire) { var self = this; var f = expire[0]; if (f === 'c' || f === 'r') { exports.cache_set(key, expire); } else { self.$cachekey = key; self.$cacheexpire = expire; } return self; }; DP.blob = function(table) { if (!table) table = 'default'; var cache = CACHE[table]; if (!cache) { var tmp = table.split('/'); cache = { db: tmp.length > 1 ? tmp[0] : 'default', table: tmp.length > 1 ? tmp[1] : tmp[0] }; CACHE[table] = cache; } var conn = CONN[cache.db]; var driver = require('./' + conn.db); return { write: function(stream, filename, callback) { if (stream instanceof Buffer) { if (typeof(filename) === 'function') { callback = filename; filename = null; } // Creates a temporary file var tmpfile = PATH.temp(Math.random().toString(16).substring(3) + '.icorm'); Fs.writeFile(tmpfile, stream, function(err) { if (err) { callback(err); return; } stream = Fs.createReadStream(tmpfile); driver.blob_write(conn, stream, filename, callback, cache); stream.on('close', () => Fs.unlink(tmpfile, NOOP)); }); } else driver.blob_write(conn, stream, filename, callback, cache); }, read: function(id, callback) { driver.blob_read(conn, id, callback, cache); }, remove: function(id, callback) { driver.blob_remove(conn, id, callback, cache); } }; }; DP.output = function(val) { this.$output = val; return this; }; function debug(val) { console.log('ICORM --->', val); } DP.debug = function() { this.$debug = debug; return this; }; DP.log = DP.audit = function() { var arg = []; var self = this; for (var i = 0; i < arguments.length; i++) arg.push(arguments[i]); self.$commands.push({ type: 'audit', arg: arg }); if (!self.busy) { self.$op && clearImmediate(self.$op); self.$op = setImmediate(self.$next); } return self; }; DP.invalid = function(name, err) { var self = this; self.$errors.push(name, err); return self; }; DP.kill = function(reason) { var self = this; self.$commands.length = 0; reason && self.$errors.push(reason); return self; }; DP.done = function($, callback, param) { this.$callback = function(err, response) { if (err) $.invalid(err); else callback(response, param); }; return this; }; DP.callback = function(fn) { var self = this; if (typeof(fn) === 'function') { self.$callback = fn; return self; } else { self.$ = fn; return new Promise(function(resolve, reject) { self.$resolve = resolve; self.$reject = reject; }); } }; DP.data = function(fn, param) { var self = this; self.$callbackok = fn; self.$callbackokparam = param; return self; }; DP.fail = function(fn, param) { var self = this; self.$callbackno = fn; self.$callbacknoparam = param; return self; }; DP.get = function(path) { var self = this; path = path.split('.'); return function() { var data = self.$outputall; var p, tmp; for (var i = 0; i < path.length - 1; i++) { p = path[i]; if (data && data[p] != null) { data = data[p]; } else { data = null; break; } } p = path[path.length - 1]; if (data instanceof Array) { tmp = []; for (var i = 0; i < data.length; i++) { var val = data[i][p]; if (val != null) tmp.push(val); } return tmp; } else return data ? data[p] : null; }; }; DP.next = function() { var self = this; if (self.$skip) return; var cmd = self.$commands.shift(); logger && loggerend(self); if (self.$op) { clearImmediate(self.$op); self.$op = null; } var stop = false; if (cmd) { if (cmd.builder && self.prev && self.prev.builder) { if (cmd.builder.$prevfilter) { for (var i = 0; i < self.prev.builder.$commands.length; i++) cmd.builder.$commands.push(self.prev.builder.$commands[i]); } if (cmd.builder.$prevfields && self.prev.builder.options.fields) cmd.builder.options.fields = self.prev.builder.options.fields; } if (cmd.builder && cmd.builder.$joinmeta) { if (!cmd.builder.$joinmeta.can) { self.$commands.push(cmd); setImmediate(self.$next); return; } } if (cmd.builder && cmd.builder.disabled) { setImmediate(self.$next); return; } if (cmd.type === 'audit') { auditwriter && auditwriter.apply(self, cmd.arg); self.$op && clearImmediate(self.$op); self.$op = setImmediate(self.$next); } else if (cmd.type === 'task') { cmd.value.call(self, self.$outputall, self.$lastoutput); if (self.$errors.length) { self.$commands = null; if (self.$) { self.$.invalid(self.$errors); self.$ = null; } if (self.$callback) { try { self.$callback(self.$errors, null); self.$callback = null; } catch (e) { self.unexpected(e); } } if (self.$callbackno) { try { self.$callbackno(self.$errors, self.$callbacknoparam); self.$callbacknoparam = self.$callbackno = null; } catch (e) { self.unexpected(e); } } self.forcekill(); } else { self.$op && clearImmediate(self.$op); self.$op = setImmediate(self.$next); } } else if (cmd.type === 'validate') { if (cmd.value == null) { if (self.$lasterror) stop = true; } else { var type = typeof(cmd.value); switch (type) { case 'function': var val = cmd.value(self.$lastoutput, self.$output); if (typeof(val) === 'string') { stop = true; self.$errors.push(val); } break; case 'string': case 'number': if (type === 'number') cmd.value += ''; if (self.$lastoutput instanceof Array) { if (cmd.reverse) { if (self.$lastoutput.length) { self.$errors.push(cmd.value); stop = true; } } else { if (!self.$lastoutput.length) { self.$errors.push(cmd.value); stop = true; } } } else { if (cmd.reverse) { if (self.$lastoutput) { self.$errors.push(cmd.value); stop = true; } } else { if (!self.$lastoutput) { self.$errors.push(cmd.value); stop = true; } } } break; } } if (stop) { self.$commands = null; if (self.$) { self.$.invalid(self.$errors); self.$ = null; } if (self.$callback) { try { self.$callback(self.$errors, null); self.$callback = null; } catch (e) { self.unexpected(e); } } if (self.$callbackno) { try { self.$callbackno(self.$errors, self.$callbacknoparam); self.$callbacknoparam = self.$callbackno = null; } catch (e) { self.unexpected(e); } } self.forcekill(); } else setImmediate(self.$next); } else { if (MODIFY[cmd.type] && cmd.value && typeof(cmd.value.$clean) === 'function') cmd.value = cmd.value.$clean(); var conn = CONN[cmd.conn || cmd.builder.options.db]; // Due to TextDB.query() if (!conn) conn = CONN.default; if (conn) { if (self.$cachekey) { exports.cache_get(self.$cachekey, cmd.builder.options.assign || 'default', function(err, cache) { if (cache) { cmd.builder.$callback(err, cache.response, cache.count, true); } else { logger && loggerbeg(self, cmd); require('./' + conn.db).run(conn, self, cmd); } }); } else { logger && loggerbeg(self, cmd); require('./' + conn.db).run(conn, self, cmd); } } else { var err = new Error('Connection string "' + (cmd.conn || cmd.builder.options.db) + '" is not initialized.'); if (cmd.builder) cmd.builder.$callback(err); else cmd.db.$next(err); } } self.prev = cmd; } else { self.forcekill(); var err = self.$eb ? self.$errors.items.length > 0 ? self.$errors : null : self.$errors.length > 0 ? self.$errors : null; if (self.$) { if (err) self.$.invalid(err); else { try { self.$resolve(self.$output); } catch (e) { self.unexpected(e); } } self.$reject = self.$resolve = null; self.$ = null; } if (self.$callback) { try { self.$callback(err, self.$output); self.$callback = null; } catch (e) { self.unexpected(e); } } if (err) { if (self.$callbackno) { self.$callbackno(err, self.$callbacknoparam); self.$callbacknoparam = self.$callbackno = null; } } else { if (self.$callbackok) { self.$callbackok(self.$output, self.$callbackokparam); self.$callbackokparam = self.$callbackok = null; } } } return self; }; DP.unexpected = function(e) { this.forcekill(); throw e; }; DP.forcekill = function() { var self = this; if (self.$conn) { self.closed = true; for (var key in self.$conn) { var item = self.$conn[key]; if (item) { item.$$destroy && item.$$destroy(item); self.$conn[key] = null; } } } }; function loggerbeg(self, cmd) { cmd.ts = new Date(); self.$logger = cmd; } function loggerend(self) { if (self.$logger) { var ln = (self.$logger.builder.options.db === 'default' ? '' : (self.$logger.builder.options.db + '/')) + (self.$logger.builder.options.table || ''); NOW = new Date(); logger(NOW.format('yyyy-MM-dd HH:mm:ss'), 'ICORM logger: ' + (ln ? (ln + '.') : '') + self.$logger.type + '()', self.$logger.builder.$count + 'x', ((NOW - self.$logger.ts) / 1000) + 's'); self.$logger = null; } } DP.make = function(fn) { var self = this; fn.call(self, self); return self; }; DP.all = DP.find = function(table) { var self = this; var builder = new QueryBuilder(self, 'find'); builder.table(table); self.$commands.push({ type: 'find', builder: builder }); if (!self.$joinmeta && !self.busy) { self.$op && clearImmediate(self.$op); self.$op = setImmediate(self.$next); } return builder; }; DP.bigFind = function(table) { var self = this; var builder = new QueryBuilder(self, 'bigFind'); builder.table(table); self.$commands.push({ type: 'bigFind', builder: builder }); if (!self.$joinmeta && !self.busy) { self.$op && clearImmediate(self.$op); self.$op = setImmediate(self.$next); } return builder; }; DP.dif = DP.diff = function(table, form, prop) { var self = this; var builder = new QueryBuilder(self, 'diff'); builder.table(table); self.$commands.push({ type: 'diff', builder: builder, form: form, key: prop || 'id' }); if (!self.busy) { self.$op && clearImmediate(self.$op); self.$op = setImmediate(self.$next); } return builder; }; DP.task = function(fn) { this.$commands.push({ type: 'task', value: fn }); return this; }; DP.list = DP.listing = function(table, improved) { var self = this; var builder = new QueryBuilder(self, 'list'); builder.table(table); //builder.options.take = 100; self.$commands.push({ type: 'list', builder: builder, improved: improved }); if (!self.busy) { self.$op && clearImmediate(self.$op); self.$op = setImmediate(self.$next); } return builder; }; DP.listCollections = function(db) { var self = this; var builder = new QueryBuilder(self, 'listCollections'); self.$commands.push({ type: 'listCollections', builder: builder }); if (!self.busy) { self.$op && clearImmediate(self.$op); self.$op = setImmediate(self.$next); } return builder; }; DP.listDatabases = function() { var self = this; var builder = new QueryBuilder(self, 'listDatabases'); self.$commands.push({ type: 'listDatabases', builder: builder }); if (!self.busy) { self.$op && clearImmediate(self.$op); self.$op = setImmediate(self.$next); } return builder; }; DP.pipeLines = function(table, improved) { var self = this; var builder = new QueryBuilder(self, 'pipeLines'); builder.table(table); self.$commands.push({ type: 'pipeLines', builder: builder, improved: improved }); if (!self.busy) { self.$op && clearImmediate(self.$op); self.$op = setImmediate(self.$next); } return builder; }; DP.read = DP.one = function(table) { var self = this; var builder = new QueryBuilder(self, 'read'); builder.table(table); builder.options.first = true; builder.options.take = 1; self.$commands.push({ type: 'read', builder: builder }); if (!self.busy) { self.$op && clearImmediate(self.$op); self.$op = setImmediate(self.$next); } return builder; }; DP.check = function(table) { var self = this; var builder = new QueryBuilder(self, 'check'); builder.table(table); builder.options.first = true; builder.options.take = 1; self.$commands.push({ type: 'check', builder: builder }); if (!self.busy) { self.$op && clearImmediate(self.$op); self.$op = setImmediate(self.$next); } return builder; }; DP.stream = function(table, limit, callback, done) { var self = this; var builder = new QueryBuilder(self, 'find'); builder.table(table); builder.options.take = limit; builder.options.skip = 0; var count = 0; var page = 1; var cb = function(err, response) { if (!response || response.length === 0) { // done done && done(null, count); return; } callback(response, function(stop) { if (stop) { done && done(null, count); return; } builder.db.forcekill(); builder.options.skip = limit * (page++); var db = new DBMS(builder.$errors); builder.db = db; db.$commands.push({ type: 'find', builder: builder }); db.$op && clearImmediate(db.$op); db.$op = setImmediate(db.$next); }); }; builder.callback(cb); self.$commands.push({ type: 'find', builder: builder }); if (!self.busy) { self.$op && clearImmediate(self.$op); self.$op = setImmediate(function() { var is = false; for (var i = 0; i < builder.$commands.length; i++) { var cmd = builder.$commands[i]; if (cmd.type === 'sort') { is = true; break; } } !is && builder.sort('1'); self.$next(); }); } return builder; }; DP.scalar = function(table, type, name, field) { // type: avg // type: count // type: group // type: max // type: min // type: sum var self = this; var builder = new QueryBuilder(self, 'scalar'); builder.table(table); self.$commands.push({ type: 'scalar', builder: builder, scalar: type, name: name, field: field }); if (!self.busy) { self.$op && clearImmediate(self.$op); self.$op = setImmediate(self.$next); } return builder; }; DP.count = function(table) { return this.scalar(table, 'count'); }; DP.max = function(table, prop) { return this.scalar(table, 'max', prop); }; DP.min = function(table, prop) { return this.scalar(table, 'min', prop); }; DP.avg = function(table, prop) { return this.scalar(table, 'avg', prop); }; DP.sum = function(table, prop) { return this.scalar(table, 'sum', prop); }; DP.group = function(table, prop) { return this.scalar(table, 'group', prop); }; DP.begin = function(conn) { var self = this; self.$commands.push({ type: 'transaction', db: self, conn: conn || 'default' }); return self; }; DP.commit = function(conn) { var self = this; self.$commands.push({ type: 'commit', db: self, conn: conn || 'default' }); return self; }; DP.end = function(conn) { var self = this; self.$commands.push({ type: 'end', db: self, conn: conn || 'default' }); return self; }; DP.rollback = DP.abort = function(conn) { var self = this; self.$commands.push({ type: 'rollback', db: self, conn: conn || 'default' }); return self; }; DP.save = function(table, isUpdate, obj, fn) { if (obj == null || typeof(obj) === 'function') { fn = obj; obj = isUpdate; isUpdate = !!obj.id; } var builder = isUpdate ? this.modify(table, obj) : this.insert(table, obj); fn && fn.call(builder, builder, isUpdate, builder.value); return builder; }; DP.add = DP.ins = DP.insert = function(table, value, unique) { var self = this; var builder = new QueryBuilder(self, 'insert'); builder.table(table); builder.value = value || {}; if (unique) { builder.options.first = true; builder.options.take = 1; } // Total.js schemas if (builder.value.$clean) builder.value = builder.value.$clean(); builder.$commandindex = self.$commands.push({ type: 'insert', builder: builder, unique: unique }) - 1; if (!self.busy) { self.$op && clearImmediate(self.$op); self.$op = setImmediate(self.$next); } return builder; }; DP.upd = DP.update = function(table, value, insert) { var self = this; var builder = new QueryBuilder(self, 'update'); builder.table(table); builder.value = value || {}; // Total.js schemas if (builder.value.$clean) builder.value = builder.value.$clean(); builder.$commandindex = self.$commands.push({ type: 'update', builder: builder, insert: insert }) - 1; if (!self.busy) { self.$op && clearImmediate(self.$op); self.$op = setImmediate(self.$next); } return builder; }; DP.mod = DP.modify = function(table, value, insert) { var self = this; var builder; if (typeof(value) === 'function') { builder = new QueryBuilder(self, 'read'); builder.table(table); builder.options.first = true; builder.options.take = 1; builder.$commandindex = self.$commands.push({ type: 'modify2', builder: builder, fn: value, insert: insert }) - 1; } else { builder = new QueryBuilder(self, 'modify'); builder.table(table); builder.value = value || {}; // Total.js schemas if (builder.value.$clean) builder.value = builder.value.$clean(); builder.$commandindex = self.$commands.push({ type: 'modify', builder: builder, insert: insert }) - 1; } if (!self.busy) { self.$op && clearImmediate(self.$op); self.$op = setImmediate(self.$next); } return builder; }; DP.que = DP.query = function(conn, query, value) { if (query == null || typeof(query) === 'object') { value = query; query = conn; conn = null; } var self = this; var builder = new QueryBuilder(self, 'query'); builder.options.db = conn || 'default'; self.$commands.push({ type: 'query', builder: builder, query: query, value: value }); value && (builder.options.params = true); if (!self.busy) { self.$op && clearImmediate(self.$op); self.$op = setImmediate(self.$next); } return builder; }; DP.rem = DP.remove = function(table) { var self = this; var builder = new QueryBuilder(self, 'remove'); builder.table(table); self.$commands.push({ type: 'remove', builder: builder }); if (!self.busy) { self.$op && clearImmediate(self.$op); self.$op = setImmediate(self.$next); } return builder; }; DP.err = DP.error = DP.must = DP.validate = function(err, reverse) { var self = this; self.$commands.push({ type: 'validate', value: typeof(err) === 'number' ? (err + '') : err, reverse: reverse }); return self; }; function QueryBuilder(db, type) { var self = this; // cloning if (db instanceof QueryBuilder) { self.db = db.db; self.$ormprimary = db.$ormprimary; self.$ormprimaryremove = db.$ormprimaryremove; self.$primarykey = db.$primarykey; self.$commands = db.$commands.slice(0); self.options = clone(db.options); } else { self.db = db; self.$commands = []; self.options = { db: 'default', type: type, take: 0, skip: 0, first: false, fields: null, dynamic: false }; } } function clone(obj) { var keys = Object.keys(obj); var o = {}; for (var i = 0; i < keys.length; i++) o[keys[i]] = obj[keys[i]]; return o; } const QB = QueryBuilder.prototype; const NOOP = function(){}; QB.promise = promise; QB.cache = function(key, expire) { var self = this; self.db.cache(key, expire); return self; }; QB.primarykey = function(key) { var self = this; self.$primarykey = key; return self; }; QB.prevfilter = function() { var self = this; self.$prevfilter = 1; return self; }; QB.custom = function(fn) { this.$commands.push({ type: 'custom', fn: fn }); return this; }; QB.prevfields = function() { var self = this; self.$prevfields = 1; return self; }; QB.use = function(name, arg) { if (TEMPLATES[name]) TEMPLATES[name](this, arg); return this; }; QB.get = function(path) { return this.db.get(path); }; QB.log = function(msg, user) { var self = this; if (auditwriter) { self.db.log.apply(self.db, arguments); return self; } if (msg) { NOW = new Date(); self.$log = (self.$log ? self.$log : '') + NOW.format('yyyy-MM-dd HH:mm:ss') + ' | ' + self.options.table.padRight(25) + ': ' + (user ? '[' + user.padRight(20) + '] ' : '') + msg + '\n'; } else if (self.$log) { Fs.appendFile(F.path.logs('icorm.log'), self.$log, NOOP); self.$log = null; } return self; }; QB.table = function(table) { var self = this; var cache = CACHE[table]; if (!cache) { var tmp = table.split('/'); cache = { db: tmp.length > 1 ? tmp[0] : 'default', table: tmp.length > 1 ? tmp[1] : tmp[0] }; cache.type = CONN[cache.db] ? CONN[cache.db].type : ''; CACHE[table] = cache; } self.options.path = table; self.options.db = cache.db; self.options.table = cache.table; self.options.dbname = cache.type; return self; }; QB.conn = function(name) { var self = this; self.options.db = name; return self; }; QB.orm = function(primary) { var self = this; self.$orm = 1; self.$ormprimary = primary || ''; if (primary && self.options.fields && self.options.fields.indexOf(primary) === -1) { self.options.fields.push(primary); self.$ormprimaryremove = 1; } return self; }; QB.$callback = function(err, value, count, iscache) { var self = this; var opt = self.options; self.$log && self.log(); if (logger) self.$count = value instanceof Array ? value.length : value != null ? 1 : 0; if (opt.type === 'list') { value = { items: value, count: count }; value.page = (opt.skip / opt.take) + 1; value.limit = opt.take; value.pages = Math.ceil(count / value.limit); } if (value) { if (self.$orm) { self.$orm = 2; if (value instanceof Array) { for (var i = 0; i < value.length; i++) { if (opt.fieldsrem) { for (var j = 0; j < opt.fieldsrem.length; j++) value[i][opt.fieldsrem[j]] = undefined; } value[i].dbms = new QueryBuilder(self); value[i].dbms.value = value[i]; } } else { if (opt.fieldsrem) { for (var j = 0; j < opt.fieldsrem.length; j++) value[opt.fieldsrem[j]] = undefined; } value.dbms = new QueryBuilder(self); value.dbms.value = value; } } else if (opt.fieldsrem) { if (value instanceof Array) { for (var i = 0; i < value.length; i++) { for (var j = 0; j < opt.fieldsrem.length; j++) value[i][opt.fieldsrem[j]] = undefined; } } else { for (var j = 0; j < opt.fieldsrem.length; j++) value[opt.fieldsrem[j]] = undefined; } } } if (err) { if (!self.$joinmeta && self.$) { self.$.invalid(err); self.$ = null; } try { opt.callback && opt.callback(err, value, count); } catch (e) { self.db.unexpected(e); } self.db.$errors.push(err); self.db.$lastoutput = null; self.db.$outputall[opt.table] = null; if (opt.callbackno) { try { opt.callbackno(err, opt.callbacknoparam); } catch (e) { self.db.unexpected(e); } opt.callbacknoparam = opt.callbackno = null; } self.db.$lasterror = err; } else { if (!self.$joinmeta) { self.db.$outputall[opt.table] = self.db.$lastoutput = value; if (opt.assign) { if (!opt.nobind) { if (self.db.$output == null) self.db.$output = {}; self.db.$output[opt.assign] = value; } self.db.$outputall[opt.assign] = value; } else if (!opt.nobind) self.db.$output = value; var ok = true; if (opt.validate) { if (opt.validatereverse) { if (value instanceof Array) { if (value.length) { self.db.$errors.push(opt.validate); ok = false; } } else if (value) { self.db.$errors.push(opt.validate); ok = false; } } else { if (value instanceof Array) { if (!value.length) { self.db.$errors.push(opt.validate); ok = false; } } else if (!value) { self.db.$errors.push(opt.validate); ok = false; } } } } if (!self.$joinmeta && self.$) { err = ok ? null : opt.validate; if (err) self.$.invalid(err); else { try { self.$resolve(value); } catch (e) { self.db.unexpected(e); } } self.$reject = self.$resolve = null; } if (opt.callback) { try { opt.callback(ok ? null : opt.validate, value, count); } catch (e) { self.db.unexpected(e); } } if (ok) { if (opt.callbackok) { try { opt.callbackok(value, opt.callbackokparam); } catch (e) { self.db.unexpected(e); } } } else if (opt.callbackno) { try { opt.callbackno(opt.validate, opt.callbacknoparam); } catch (e) { self.db.unexpected(e); } opt.callbackno = opt.callbacknoparam = null; } } if (self.db.$cachekey && !iscache && !CACHEBLACKLIST[opt.type]) exports.cache_set(self.db.$cachekey, self.db.$cacheexpire, opt.assign || 'default', { response: value, count: count }); if (self.$orm) opt.callbacknoparam = opt.callbackokparam = opt.callbackok = opt.callbackno = opt.callback = undefined; if (!self.busy) { self.db.$op && clearImmediate(self.db.$op); self.db.$op = setImmediate(self.db.$next); } }; QB.nobind = QB.unbind = function() { this.options.nobind = true; return this; }; QB.make = function(fn) { var self = this; fn.call(self, self); return self.db; }; QB.inc = function(prop, value) { var self = this; if (self.$commandindex == null) throw new Error('This QueryBuilder.inc() is supported for INSERT/UPDATE/MODIFY operations.'); var cmd = self.db.$commands[self.$commandindex]; if (value > 0) prop = '+' + prop; else { prop = '-' + prop; value = value * -1; } if (cmd.value[prop]) cmd.value += value; else cmd.value[prop] = value; return self; }; QB.set = QB.upd = function(prop, value) { var self = this; if (value === undefined) { self.options.assign = prop == null ? '' : prop; return self; } if (self.$commandindex == null) throw new Error('This QueryBuilder.inc() is supported for INSERT/UPDATE/MODIFY operations.'); self.model[prop] = value; return self; }; // Is same as `set()` without `value` QB.assign = function(prop) { var self = this; self.options.assign = prop == null ? '' : prop; return self; }; QB.eq = function() { var model = arguments[arguments.length - 1]; for (var i = 0; i < arguments.length - 1; i++) this.where(arguments[i], model[arguments[i]]); return this; }; QB.inarray = function(name, value, ornull) { var self = this; if (!(value instanceof Array)) value = [value]; self.query((ornull ? ('array_length(' + name + ',1) IS NULL OR ') : '') + name + ' && $1', [value]); return self; }; QB.where = function(name, compare, value) { if (value === undefined) { value = compare; compare = '='; } else { compare = COMPARE[compare]; if (compare == null) throw new Error('DBMS: comparer "' + compare + '" is not supported for QueryBuilder.'); } var self = this; self.$commands.push({ type: 'where', name: name, value: value, compare: compare }); return self; }; QB.owner = function(name, value, member, condition) { var self = this; self.$commands.push({ type: 'owner', name: name, value: value && value instanceof Object ? (value.user ? value.user.id : value.id) : value, member: member, condition: condition }); return self; }; QB.permit = function(name, type, value, useridfield, userid, must) { // type: R read // type: W write // type: D delete var self = this; var arr = []; for (var i = 0; i < value.length; i++) arr.push(type + value[i]); self.$commands.push({ type: 'permit', name: name, value: arr, useridfield: useridfield, userid: userid, must: must }); return self; }; QB.id = function(value, field) { var self = this; if (value instanceof Array) self.$commands.push({ type: 'in', name: 'id', value: value, field: field }); else self.$commands.push({ type: 'where', name: 'id', value: value, compare: '=' }); return self; }; QB.userid = function(value) { this.$commands.push({ type: 'where', name: 'userid', value: value, compare: '=' }); return this; }; QB.undeleted = function() { this.$commands.push({ type: 'where', name: 'isremoved=FALSE', compare: '=' }); return this; }; QB.in = function(name, value, field) { var self = this; self.$commands.push({ type: 'in', name: name, value: value, field: field }); return self; }; QB.notin = function(name, value) { var self = this; self.$commands.push({ type: 'notin', name: name, value: value }); return self; }; QB.between = function(name, a, b) { var self = this; self.$commands.push({ type: 'between', name: name, a: a, b: b }); return self; }; QB.search = function(name, value, compare) { var self = this; self.$commands.push({ type: 'search', name: name, value: value, compare: compare == null ? '*' : compare }); return self; }; QB.searchfull = function(value) { var self = this; self.$commands.push({ type: 'searchfull', value: value }); return self; }; QB.searchall = function(name, value) { var self = this; if (!(value instanceof Array)) value = value.split(' '); self.$commands.push({ type: 'searchall', name: name, value: value }); return self; }; QB.fulltext = function(name, value, weight) { var self = this; self.$commands.push({ type: 'fulltext', name: name, value: value, weight: weight }); return self; }; QB.regexp = function(name, value) { var self = this; self.$commands.push({ type: 'regexp', name: name, value: value }); return self; }; QB.contains = function(name) { var self = this; self.$commands.push({ type: 'contains', name: name }); return self; }; QB.empty = function(name) { var self = this; self.$commands.push({ type: 'empty', name: name }); return self; }; QB.first = function() { var self = this; self.options.first = true; self.take(1); return self; }; QB.sort = function(name, desc) { var self = this; self.$commands.push({ type: 'sort', name: name, desc: desc == true || desc === 'desc' }); return self; }; QB.take = function(value) { var self = this; self.options.take = value; return self; }; QB.skip = function(value) { var self = this; self.options.skip = value; return self; }; QB.equal = function(val, model) { var self = this; var keys = val.split(','); if (!model) self.options.equal = []; for (var i = 0; i < keys.length; i++) { var k = keys[i][0] === ' ' ? keys[i].substring(1) : keys[i]; if (model) self.where(k, model[k]); else self.options.equal.push(k); } return self; }; QB.limit = function(value) { var self = this; self.options.take = value; return self; }; QB.page = function(page, limit) { var self = this; if (limit) self.options.take = limit; self.options.skip = (page - 1) * self.options.take; return self; }; QB.paginate = function(page, limit, maxlimit) { var self = this; var limit2 = +(limit || 0); var page2 = (+(page || 0)) - 1; if (page2 < 0 || !page2) page2 = 0; if (maxlimit && limit2 > maxlimit) limit2 = maxlimit; if (!limit2) limit2 = maxlimit; self.options.skip = page2 * limit2; self.options.take = limit2; return self; }; QB.callback = function(callback) { var self = this; // Because of JOINS if (self.$ && self.$joinmeta && self.$joinmeta.owner && !self.$joinmeta.promise) { self.$joinmeta.promise = true; self.$joinmeta.owner.$ = self.$; self.$joinmeta.owner.$resolve = self.$resolve; self.$joinmeta.owner.$reject = self.$reject; } if (self.options.callback && self.$joinmeta && self.$joinmeta.owner && !self.$joinmeta.callback) { self.$joinmeta.callback = true; self.$joinmeta.owner.options.callback = self.options.callback; } if (typeof(callback) === 'function') { self.options.callback = callback; return self; } self.$ = callback; return new Promise(function(resolve, reject) { self.$resolve = resolve; self.$reject = reject; }); }; QB.done = function($, callback, param) { return this.callback(function(err, response) { if (err) $.invalid(err); else callback(response, param); }); }; QB.debug = function() { this.db.$debug = debug; return this; }; QB.data = function(fn, param) { var self = this; // Because of JOINS if (self.$joinmeta && self.$joinmeta.owner) { self.$joinmeta.owner.options.callbackok = fn; self.$joinmeta.owner.options.callbackokparam = param; } else { self.options.callbackok = fn; self.options.callbackokparam = param; } return self; }; QB.fail = function(fn, param) { var self = this; // Because of JOINS if (self.$joinmeta && self.$joinmeta.owner) { self.$joinmeta.owner.options.callbackno = fn; self.$joinmeta.owner.options.callbacknoparam = param; } else { self.options.callbackno = fn; self.options.callbacknoparam = param; } return self; }; QB.on = function(a, b) { var self = this; self.$joinmeta.a = a; self.$joinmeta.b = b; return self; }; QB.join = function(field, table) { if (table == null) table = field; var self = this; var builder = new QueryBuilder(self.db, 'find'); builder.table(table); builder.$joinmeta = { unique: new Set(), field: field, a: '', b: '', owner: self.$joinmeta ? self.$joinmeta.owner : self }; if (self.$joins) self.$joins.push(builder); else self.$joins = [builder]; self.db.$commands.push({ type: 'find', builder: builder }); return builder; }; QB.err = QB.error = QB.must = QB.validate = function(err, reverse) { var self = this; self.options.validate = (typeof(err) === 'number' ? (err + '') : err) || 'unhandled exception'; self.options.validatereverse = reverse; return self; }; QB.insert = function(callback, params) { var self = this; self.options.insert = callback; self.options.insertparams = params; return self; }; QB.code = QB.query = function(q, value) { var self = this; if (!self.options.params && !!value) self.options.params = true; self.$commands.push({ type: 'query', query: q, value: value }); return self; }; QB.or = function(fn) { var self = this; var beg = self.$commands.push({ type: 'or' }); fn.call(self, self); var end = self.$commands.push({ type: 'end' }); if ((end - beg) === 1) { self.$commands.pop(); self.$commands.pop(); } return self; }; QB.subquery = function(name, query) { if (query == null) { query = name; name = null; } var self = this; if (!self.options.subquery) self.options.subquery = []; self.options.subquery.push({ name: name, query: query }); return self; }; QB.fields = function(fields) { var self = this; var arr = arguments; var is = false; if (arr.length === 1) { if (FIELDS[fields]) { self.options.fields = FIELDS[fields]; return self; } if (fields.indexOf(',') !== -1) { arr = fields.split(','); is = true; } } if (!self.options.fields) self.options.fields = []; for (var i = 0; i < arr.length; i++) { var field = arr[i][0] === ' ' ? arr[i].trim() : arr[i]; self.options.fields.push(field); } if (is) FIELDS[fields] = self.options.fields; return self; }; QB.language = function(language, prefix, skip) { var self = this; if (skip && language && language === skip) language = null; self.options.language = (language ? ((prefix == null ? global.DBMS.languageprefix : (prefix || '')) + language) : ''); self.options.islanguage = true; return self; }; QB.year = function(name, compare, value) { if (value === undefined) { value = compare; compare = '='; } else { compare = COMPARE[compare]; if (compare == null) throw new Error('DBMS: comparer "' + compare + '" is not supported for QueryBuilder.'); } var self = this; self.$commands.push({ type: 'year', name: name, value: value, compare: compare }); return self; }; QB.month = function(name, compare, value) { if (value === undefined) { value = compare; compare = '='; } else { compare = COMPARE[compare]; if (compare == null) throw new Error('DBMS: comparer "' + compare + '" is not supported for QueryBuilder.'); } var self = this; self.$commands.push({ type: 'month', name: name, value: value, compare: compare }); return self; }; QB.day = function(name, compare, value) { if (value === undefined) { value = compare; compare = '='; } else { compare = COMPARE[compare]; if (compare == null) throw new Error('DBMS: comparer "' + compare + '" is not supported for QueryBuilder.'); } var self = this; self.$commands.push({ type: 'day', name: name, value: value, compare: compare }); return self; }; QB.hour = function(name, compare, value) { if (value === undefined) { value = compare; compare = '='; } else { compare = COMPARE[compare]; if (compare == null) throw new Error('DBMS: comparer "' + compare + '" is not supported for QueryBuilder.'); } var self = this; self.$commands.push({ type: 'hour', name: name, value: value, compare: compare }); return self; }; QB.minute = function(name, compare, value) { if (value === undefined) { value = compare; compare = '='; } else { compare = COMPARE[compare]; if (compare == null) throw new Error('DBMS: comparer "' + compare + '" is not supported for QueryBuilder.'); } var self = this; self.$commands.push({ type: 'minute', name: name, value: value, compare: compare }); return self; }; QB.date = function(name, compare, value) { if (value === undefined) { value = compare; compare = '='; } else { compare = COMPARE[compare]; if (compare == null) throw new Error('DBMS: comparer "' + compare + '" is not supported for QueryBuilder.'); } var self = this; self.$commands.push({ type: 'date', name: name, value: value, compare: compare }); return self; }; // ORM QB.wait = function() { var self = this; self.db.$skip = true; self.db.$op && clearImmediate(self.db.$op); return self; }; // ORM QB.remove = function(callback) { var self = this; var isnew = false; if (!self.db || self.db.closed) { isnew = true; self.db = new DBMS(); } self.$orm = 0; if (self.$ormprimary) { self.where(self.$ormprimary, self.value[self.$ormprimary] || null); if (self.$ormprimaryremove) self.value[self.$ormprimary] = undefined; } if (self.options.callbackok) self.options.callbackok = null; if (self.options.callbackno) self.options.callbackno = null; self.options.callback = callback ? callback : null; self.db.$commands.push({ type: 'remove', builder: self }); // "next" command is performend when the DBMS instance is new if (self.db.$skip || isnew) { self.db.$skip = false; self.db.$op && clearImmediate(self.db.$op); self.db.$op = setImmediate(self.db.$next); } return self; }; // ORM QB.continue = function() { var self = this; self.db.$skip = false; self.db.$op && clearImmediate(self.db.$op); self.db.$op = setImmediate(self.db.$next); return self; }; // ORM QB.copy = function(val, existing) { var self = this; var keys = Object.keys(val); for (var i = 0; i < keys.length; i++) { var key = keys[i]; if (key !== 'dbms' && key !== self.$ormprimary && (!existing || self.value[key] !== undefined)) self.value[key] = val[key]; } return self; }; // ORM QB.modified = function(val) { var self = this; var keys = Object.keys(self.value); var data; for (var i = 0; i < keys.length; i++) { var key = keys[i]; var a = val[key]; if (a === undefined) continue; var b = self.value[key]; if (a === b) continue; if ((a instanceof Date) && (b instanceof Date)) { if (a.getTime() === b.getTime()) continue; } if (a && b && a instanceof Object && b instanceof Object) { // array or object if (JSON.stringify(a) === JSON.stringify(b)) continue; } if (!data) data = {}; data[key] = a; } if (data) { data[self.$ormprimary] = self.value[self.$ormprimary]; self.value = data; } else self.value = null; return !!data; }; QB.replace = function(val) { var self = this; var id = self.$ormprimary ? self.value[self.$ormprimary] : null; self.value = val; if (self.$ormprimary) self.value[self.$ormprimary] = id; return self; }; // ORM QB.save = function(callback) { var self = this; if (!self.value) { callback(null, 0); return; } var isnew = false; if (!self.db || self.db.closed) { isnew = true; self.db = new DBMS(); } self.$orm = 0; self.db.$commandindex = self.db.$commands.push({ type: 'modify', builder: self }) - 1; self.options.fields = null; if (self.options.callbackok) self.options.callbackok = null; if (self.options.callbackno) self.options.callbackno = null; if (self.$ormprimary) { self.where(self.$ormprimary, self.value[self.$ormprimary] || null); if (self.$ormprimaryremove) self.value[self.$ormprimary] = undefined; } self.options.callback = callback ? callback : null; // "next" command is performed when the DBMS instance is new if (self.db.$skip || isnew) { self.db.$skip = false; self.db.$op && clearImmediate(self.db.$op); self.db.$op = setImmediate(self.db.$next); } return self; }; exports.QueryBuilder = QueryBuilder; exports.DBMS = DBMS; exports.make = function(fn) { var self = new DBMS(); fn.call(self, self); return self; }; exports.init = function(name, connection, onerror) { if (connection == null) { connection = name; name = 'default'; } if ((onerror === true || connection === true) && global.F) { onerror = function(err, sql, builder) { F.error(new Error(err.toString() + ': ' + sql), 'DBMS'); }; } if (connection === true || typeof(connection) === 'function') { if (!onerror) onerror = connection; connection = name; name = 'default'; var onerror2 = onerror; onerror = function(err, sql, builder) { onerror2(new Error(err.toString() + ': ' + sql), builder); }; } // Total.js if (connection === 'nosql' || connection === 'table') { CONN[name] = { id: name, db: 'total', type: connection }; return exports; } if (connection === 'textdb' || connection === 'inmemory') { CONN[name] = { db: connection }; return exports; } var opt = Url.parse(connection); var q = Qs.parse(opt.query); var tmp = {}; var arr; var pooling = q.pooling ? q.pooling !== '0' && q.pooling !== 'false' && q.pooling !== 'off' : true; var native = q.native === '1' || q.native === 'true' || q.native === 'on'; switch (opt.protocol) { case 'postgresql:': case 'postgres:': case 'postgre:': case 'pg:': if (opt.auth) { arr = opt.auth.split(':'); tmp.user = arr[0] || ''; tmp.password = arr[1] || ''; } tmp.host = opt.hostname; tmp.port = opt.port; tmp.database = opt.pathname.split('/')[1]; tmp.ssl = q.ssl === '1' || q.ssl === 'true' || q.ssl === 'on'; tmp.max = +(q.max || '4'); tmp.min = +(q.min || '2'); tmp.idleTimeoutMillis = +(q.timeout || '1000'); tmp.native = native; tmp.pooling = pooling; CONN[name] = { id: name, db: 'pg', options: tmp, onerror: onerror, type: 'pg' }; // Due to PG_ESCAPE require('./pg'); break; case 'mysql:': case 'mariadb:': if (opt.auth) { arr = opt.auth.split(':'); tmp.user = arr[0] || ''; tmp.password = arr[1] || ''; } tmp.host = opt.hostname; tmp.port = opt.port; tmp.database = opt.pathname.split('/')[1]; tmp.ssl = q.ssl === '1' || q.ssl === 'true' || q.ssl === 'on'; tmp.connectionLimit = +(q.max || '4'); // tmp.min = +(q.min || '2'); tmp.waitForConnections = true; tmp.pooling = pooling; CONN[name] = { id: name, db: 'mysql', options: tmp, onerror: onerror, type: 'mysql' }; // Due to PG_ESCAPE require('./mysql'); break; case 'mongodb+srv:': case 'mongodb:': case 'mongo:': // Para MongoDB, no usamos max/maxPoolSize en la URL de conexión // Lo aplicamos directamente en las opciones del cliente // Limpiar la URL de conexión para eliminar parámetros no soportados var mongoURI = connection; var maxPoolSize = 10; // valor por defecto // Extraer el nombre de la base de datos correctamente // En una URL de replicaset: mongodb://user:pass@host1,host2,host3/dbname?options // El nombre de la BD está después de la última '/' y antes del '?' var dbName = ''; var fullUrl = connection; // Encontrar la última ocurrencia de '/' antes de '?' var lastSlashIndex = fullUrl.lastIndexOf('/'); var questionMarkIndex = fullUrl.indexOf('?', lastSlashIndex); if (lastSlashIndex !== -1) { if (questionMarkIndex !== -1) { // Hay un '?' después de la última '/' dbName = fullUrl.substring(lastSlashIndex + 1, questionMarkIndex); } else { // No hay '?' después de la última '/' dbName = fullUrl.substring(lastSlashIndex + 1); } } // Validar que el nombre de la base de datos no contenga caracteres ilegales if (dbName && dbName.indexOf('.') !== -1) { console.error('Error: El nombre de la base de datos no puede contener puntos (.):', dbName); // Eliminar los puntos del nombre de la base de datos dbName = dbName.replace(/\./g, '_'); console.log('Nombre de base de datos corregido:', dbName); } // Extraer maxPoolSize si existe en los parámetros if (q.max) { maxPoolSize = parseInt(q.max, 10); // Crear una nueva URL sin el parámetro max mongoURI = removeParamFromURL(connection, 'max'); } else if (q.maxPoolSize) { maxPoolSize = parseInt(q.maxPoolSize, 10); // Crear una nueva URL sin el parámetro maxPoolSize mongoURI = removeParamFromURL(connection, 'maxPoolSize'); } CONN[name] = { id: name, db: 'mongo', options: mongoURI, database: dbName, // Usar el nombre de base de datos extraído correctamente onerror: onerror, type: 'mongodb', maxPoolSize: maxPoolSize // Configuración de maxPoolSize separada }; break; case 'inmemory': CONN[name] = { id: name, db: 'inmemory', options: connection, table: opt.host, database: opt.host, onerror: onerror, type: 'textdb' }; break; case 'textdb:': CONN[name] = { id: name, db: 'textdb', options: connection, table: opt.host, database: opt.host, onerror: onerror, type: 'textdb' }; break; case 'opendb:': CONN[name] = { id: name, db: 'opendb', options: connection, table: opt.host, url: connection.replace('opendb:', q.ssl === '1' || q.ssl === 'true' || q.ssl === 'on' ? 'wss:' : 'ws:'), database: opt.host, onerror: onerror, type: 'opendb' }; break; } return exports; }; global.DBMS = function(err) { if (typeof(err) === 'function') { var db = new exports.DBMS(); err.call(db, db); } else return new exports.DBMS(err); }; global.DBMS.languageprefix = '_'; global.DBMS.audit = function(fn) { auditwriter = fn; }; global.DBMS.logger = function(fn) { if (fn === undefined) logger = console.log; else logger = fn; }; global.DBMS.template = function(name, fn) { TEMPLATES[name] = fn; }; global.DBMS.measure = function(callback, file) { if (typeof(callback) === 'boolean') { var tmp = file; file = callback; callback = tmp; } var stats = { insert: 0, inserttotal: 0, insertidle: 0, select: 0, selecttotal: 0, selectidle: 0, update: 0, updatetotal: 0, updateidle: 0, query: 0, querytotal: 0, queryidle: 0, delete: 0, deletetotal: 0, deleteidle: 0, count: 0, total: 0, ticks: 0 }; var usage = {}; ON('dbms', function(type, table, db) { var now = Date.now(); stats.idle = stats.ticks ? (now - stats.ticks) : null; switch (type) { case 'insert': stats.insert++; stats.inserttotal++; stats.count++; stats.total++; stats.insertidle = stats.insertticks ? (now - stats.insertticks) : null; stats.insertticks = now; break; case 'select': stats.select++; stats.selecttotal++; stats.count++; stats.total++; stats.selectidle = stats.selectticks ? (now - stats.selectticks) : null; stats.selectticks = now; break; case 'query': stats.query++; stats.querytotal++; stats.count++; stats.total++; stats.queryidle = stats.queryticks ? (now - stats.queryticks) : null; stats.queryticks = now; table = table.substring(0, 30); break; case 'udpate': stats.update++; stats.updatetotal++; stats.count++; stats.total++;