UNPKG

brest-maria

Version:
443 lines (400 loc) 15.4 kB
var _ = require("lodash"), async = require("async"), EventEmitter = require('events').EventEmitter, util = require("util"); /** * Table constructor * @param {DB} db * @param {Object} settings * @constructor */ var Table = function(db, settings) { var self = this; self.db = db; self.settings = settings; self.name = self.settings.table; self.defaultIds = self.settings.defaultIds; if (_.isUndefined(self.settings.queries)) self.settings.queries = {}; self.columns = {}; self.primary = []; self.dynamic = self.settings.dynamic || []; self.p = {}; self.db.query(util.format('SHOW COLUMNS FROM `%s`', self.name), function(err, columns){ if (err) { console.log(err); throw err; } else { var defaultIds = []; _.each(columns, function(column){ self.columns[column.Field] = { isNull: (column.Null == 'YES'), isPrimary: (column.Key == 'PRI'), isDynamic: (self.dynamic.indexOf(column.Field) > -1) }; if (column.Key == 'PRI') { self.primary.push(column.Field); defaultIds.push(util.format('`%s`.`%s` = :%s',self.name, column.Field, column.Field)); } }); if (defaultIds.length){ self.defaultIds = defaultIds.join(' AND '); } else { console.log('Failed to set default id for table '+self.name); } //Set default filters for all table columns var defaultFilters = {}; _.each(self.columns, function(props, column){ defaultFilters[column] = {where: util.format(" AND `%s`.`%s` = ?", self.name, column)}; defaultFilters[column+'s'] = {where: util.format(" AND `%s`.`%s` IN (?)", self.name, column)}; }); //Check for default values set in custom modules and for settings self.filters = _.defaults(self.filters || {}, self.settings.filters); //Mix with default filters, if latter are not overridden self.filters = _.defaults(self.filters || {}, defaultFilters); //If the queries are already defined in model object, we use them instead of default queries self.queries = _.defaults(self.queries || {}, { row: self.settings.queries.row || util.format("SELECT {{columns}}{{select}} " + "FROM `%s`{{join}} " + "WHERE {{whereClause}}{{where}} LIMIT 1", self.name), list: self.settings.queries.list || util.format("SELECT {{columns}}{{select}} " + "FROM `%s`{{join}} " + "WHERE 1{{where}}{{order}}{{limit}}", self.name), insert: self.settings.queries.insert || util.format("INSERT INTO `%s` ({{columns}}) VALUES ({{values}}){{duplicate}}", self.name), update: self.settings.queries.update || util.format("UPDATE `%s` SET {{columns}} WHERE %s", self.name, self.defaultIds), del: self.settings.queries.del || util.format("DELETE FROM `%s` WHERE %s", self.name, self.defaultIds), count: self.settings.queries.count || util.format("SELECT COUNT(*) as cnt FROM `%s` WHERE 1 {{where}}", self.name) }); self.updatePersistent(function(err){ if (err) console.log('ERROR: persistent fields creation failed for ', self.name, 'with error:\n',err); }); } }); /** * Prevent persistent fields updates from being fired */ this.suspendPersistentUpdates = function(){ self.persistentUpdatesSuspended = true; }; /** * Make persistent updates possible again */ this.resumePersistentUpdates = function(callback){ self.persistentUpdatesSuspended = false; self.updatePersistent(function(err){ if (err) console.log('ERROR: persistent fields update failed on resume for ', self.name, 'with error:\n', err); callback(err); }); }; /** * Call persistent data update functions * @param callback */ this.updatePersistent = function(callback){ if (self.persistent && !self.persistentUpdatesSuspended) { console.log('UPDATING PERSISTENT ', self.name); async.each(_.keys(self.persistent), function(key, callback){ self.persistent[key](function(err, data){ if (err) { self.p[key] = false; callback(err); } else { self.p[key] = data; callback(); } }); }, callback) } }; this.flattenJSON = function(json, glue){ var res = glue+"("; var parts = []; _.each(json, function(value, key){ if (!_.isObject(value)) { parts.push("'"+key+"','"+ self.db.esc(value)+"'"); } else { parts.push("'"+key+"',"+self.flattenJSON(value, glue)); } }); res += parts.join(", ") + ")"; return res; }; /** * Select one row as an object * @param {int|String|Object} ids * @param {Object} filters * @param {Function} callback */ this.row = function(ids, filters, callback){ if (_.isFunction(filters)) { callback = filters; filters = {}; } if (!_.isFunction(callback)) throw new Error("Callback must be a function"); var whereClause = ""; if (!_.isObject(ids)) { var params = {}; params[self.primary[0]] = ids; ids = params; whereClause = self.defaultIds; } else { var fields = []; _.each(_.keys(ids), function(field){ fields.push(util.format("%s = :%s", field, field)) }); whereClause = fields.join(" AND "); } var columns = []; _.each(self.columns, function(column, name){ if (column.isDynamic) { columns.push(util.format('COLUMN_JSON(`%s`.`%s`) as `%s`', self.name, name, name)); } else { columns.push(util.format('`%s`.`%s`', self.name, name)) } }); var sql = self.queries.row .replace('{{columns}}', columns.join(', ')) .replace('{{whereClause}}', whereClause); sql = self.db.injectFilters(sql, filters, self.filters); self.db.query(sql, ids, function(err, rows){ if (err) callback(err); else callback(err, rows[0]); }); }; /** * Inject sorting into the request * @param sql * @param filters * @returns {*} */ this.injectSort = function(sql, filters){ if (filters['order']) { var sort = filters['order'].split(','); if (_.isArray(sort)){ var direction = 'ASC'; if (sort.indexOf('desc')>-1){ direction = 'DESC'; delete sort[sort.indexOf('desc')]; } if (sort.length) { sort.forEach(function(sortField, i, arr){ arr[i] = sortField.replace('.','`.`'); }); var fields = sort.join("`, `"); sql = sql.replace('{{order}}', util.format(" ORDER BY `%s` %s", fields, direction)); } } else throw "Failed to parse order filter" } return sql; }; /** * Inject limit into the request * @param sql * @param filters */ this.injectLimit = function(sql, filters){ if (filters['limit']) { var self = this; var limit = filters['limit'].split(','); if (_.isArray(limit)){ for (var i=0; i<limit.length; i++){ limit[i] = parseInt(limit[i]); } sql = sql.replace('{{limit}}', util.format(" LIMIT %s", limit.join(', '))) } else throw "Failed to parse limit filter" } return sql; }; /** * Select data by query as an array of objects * @param {Object} filters * @param {Function} callback */ this.list = function(filters, callback) { if (_.isFunction(filters)) { callback = filters; filters = {}; } else { if (!_.isObject(filters)) { console.log("WARNING. Invalid filters provided for " + self.name + ".list(). Object required"); console.log(filters); filters = {}; } } if (!_.isFunction(callback)) throw new Error("Callback must be a function"); var columns = []; _.each(self.columns, function (column, name) { if (column.isDynamic) { columns.push(util.format('COLUMN_JSON(`%s`.`%s`) as `%s`', self.name, name, name)); } else { columns.push(util.format('`%s`.`%s`', self.name, name)) } }); var sql = self.queries.list.replace('{{columns}}', columns.join(', ')); sql = self.injectLimit(sql, filters); sql = self.injectSort(sql, filters); sql = self.db.injectFilters(sql, filters, self.filters); return self.db.query(sql, callback); }; /** * Insert * @param {Object} data * @param {Object} options * @param {Function} callback */ this.insert = function(data, options, callback) { if (_.isFunction(options)) { callback = options; options = {}; } if (!options) options = {}; var duplicate = options.duplicate ? " ON DUPLICATE KEY UPDATE "+options.duplicate : ""; _.each(data, function(value, column){ if (!self.columns[column]) delete data[column]; if (_.isObject(value)){ if (self.columns[column].isDynamic) data[column] = self.flattenJSON(value, 'COLUMN_CREATE'); else data[column] = JSON.stringify(value); } }); var columns = _.keys(data).join(', '); var names = []; _.each(_.keys(data), function(val){ if (self.columns[val].isDynamic) { names.push(data[val]); delete data[val]; } else { names.push(':' + val) } }); var sql = self.queries.insert .replace('{{columns}}', columns) .replace('{{values}}', names) .replace('{{duplicate}}', duplicate); var q = self.db.query(sql, data, function(err){ if (err) callback(err); else { callback(null, {insert: 'success'}); self.updatePersistent(function(err){ if (err) console.log('\nERROR: persistent fields update failed for ', self.name, 'with error:\n'+err); }); } }); }; /** * Update * @param data * @param options * @param callback */ this.update = function(data, options, callback) { var values = {}; var valuesStr = []; if (_.isFunction(options)) { callback = options; options = {}; } _.each(data, function(value, column){ if (!self.columns[column]) delete data[column]; else if (!self.primary[column]) { if (self.columns[column].isDynamic && _.isObject(value)) values[column] = self.flattenJSON(value, 'COLUMN_CREATE'); else values[column] = data[column]; } }); _.each(values, function(value, column){ valuesStr.push(util.format('`%s` = :%s', column, column)); }); valuesStr = valuesStr.join(', '); var sql = self.queries.update.replace('{{columns}}', valuesStr); return self.db.query(sql, data, function(err){ if (err) callback(err); else { callback(null, {update: 'success'}); self.updatePersistent(function(err){ if (err) console.log('\nERROR: persistent fields update failed for ', self.name, 'with error:\n'+err); }); } }); }; /** * Simple deletion by primary ids * @param ids * @param callback */ this.del = function(ids, callback) { if (!_.isObject(ids)) { if (self.primary.length == 1) { var id = {}; id[self.primary[0]] = ids; ids = id; } else callback({Error: "Incorrect delete id"}); } return self.db.query(self.queries.del, ids, function(err){ if (err) callback(err); else { callback(null, {delete: 'success'}); self.updatePersistent(function(err){ if (err) console.log('\nERROR: persistent fields update failed for ', self.name, 'with error:\n'+err); }); } }); }; /** * Return the number of records matching the request. Count all records by default * @param filters * @param callback */ this.count = function(filters, callback) { if (_.isFunction(filters)) { callback = filters; filters = {}; } var sql = self.db.injectFilters(self.queries.count, filters, self.filters); self.db.query(sql, function(err, count){ if (err) callback(err); else callback(null, count[0]['cnt']); }); }; /** * Returns true if quer * * @param filters * @param callback */ this.exists = function(filters, callback) { self.count(filters, function(err, count){ callback(err, count>0); }) }; /** * Shortcut for sql query to use in extended models * * @param {String} sql * @param {Object} params * @param {Function} callback * @returns {Object} MariaDB query object* */ this.query = function(sql, params, callback) { return self.db.query(sql, params, callback); }; /** * Shortcut for sql row query to use in extended models * * @param {String} sql * @param {Object} params * @param {Function} callback * @returns {Object} MariaDB query object */ this.queryRow = function(sql, params, callback) { return self.db.queryRow(sql, params, callback) }; }; module.exports = Table;