UNPKG

caminte

Version:

ORM for every database: redis, mysql, neo4j, mongodb, rethinkdb, postgres, sqlite, tingodb

584 lines (540 loc) 17.9 kB
/** * Module dependencies */ var utils = require('../utils'); var util = require('util'); var safeRequire = utils.safeRequire; var arango = safeRequire('arangojs'); var url = require('url'); exports.initialize = function initializeSchema(schema, callback) { if (!arango) return; var s = schema.settings; if (schema.settings.url) { var uri = url.parse(schema.settings.url); s.host = uri.hostname; s.port = uri.port || '8529'; s.database = uri.pathname.replace(/^\//, ''); s.username = uri.auth && uri.auth.split(':')[0]; s.password = uri.auth && uri.auth.split(':')[1]; } s.host = s.host || 'localhost'; s.port = parseInt(s.port || '8529', 10); s.database = s.database || 'test'; if (!schema.settings.url) { var durl = schema.settings.host || 'localhost'; if (schema.settings.port) durl += ':' + schema.settings.port; var auth = ''; if (schema.settings.username) { auth = schema.settings.username; if (schema.settings.password) { auth += ':' + schema.settings.password; } } if (auth) { durl = auth + '@' + url; } durl += '/'; durl = 'http://' + durl; schema.settings.url = durl; } schema.adapter = new Arango(s, schema, callback); }; var ArangoId = function (fullId) { var parts = fullId.split('/'); this._id = parts[0]; this._rev = parts[1]; }; ArangoId.prototype.id = function () { return this._id; }; ArangoId.prototype.rev = function () { return this._rev; }; ArangoId.prototype.fullId = function () { return this._id + '/' + this._rev; }; ArangoId.prototype.setRev = function (rev) { this._rev = rev; }; function Arango(s, schema, callback) { var self = this; self.name = 'arango'; self._models = {}; self.settings = s; self.collections = {}; self.aqlQuery = arango.aqlQuery; self.client = new arango.Database(schema.settings.url); self.client.createDatabase(s.database, function (res) { // console.log('Database ' + schema.settings.database + ' created!'); }, function (err) { if (err.errorNum !== 1207) { console.log("Failed to create database: %j", err); } }); self.client.useDatabase(s.database); schema.client = self.client; process.nextTick(function () { callback(); }); } Arango.prototype.define = function (descr) { var self = this, indexes; if (!descr.settings) descr.settings = {}; self._models[descr.model.modelName] = descr; self.client.useDatabase(self.settings.database); var collection = self.client.collection(descr.model.modelName); collection.create(); self.collections[descr.model.modelName] = collection; var keys = self.getModelIndexes(descr.model.modelName); if (keys.indexes.length) { self.ensureIndex(descr.model.modelName, keys.indexes, false, function () { // created }.bind(self)); } if (keys.uniques.length) { self.ensureIndex(descr.model.modelName, keys.uniques, true, function () { // created }.bind(self)); } }; Arango.prototype.getModelIndexes = function (name) { var model = this._models[name], indexes = [], uniques = []; Object.keys(model.properties).forEach(function (k) { if (typeof model.properties[k].unique !== 'undefined') { uniques.push(k); } if (typeof model.properties[k].index !== 'undefined') { indexes.push(k); } }.bind(this)); return { indexes: indexes, uniques: uniques }; }; Arango.prototype.collection = function (name) { if (this.client.collection) { return this.client.collection(name); } else { if (!this.collections[name]) { this.collections[name] = this.client.collection(name); } return this.collections[name]; } }; /** * Update existing database collections. * @param {Function} callback */ Arango.prototype.autoupdate = function (callback) { var self = this, pending = Object.keys(self._models).length; if (!pending) { return callback && callback(); } Object.keys(self._models).forEach(function (model) { self.collection(model).create(function () { self.collection(model) .setProperties({waitForSync: true}, function () { if (--pending === 0) { setTimeout(function () { var keys = self.getModelIndexes(model); self.ensureIndex(model, keys.indexes, false, function () { return self.ensureIndex(model, keys.uniques, true, callback); }); }, 100); } }); }); }); }; /** * Re create existing database collections. * @param {Function} callback */ Arango.prototype.automigrate = function (callback) { var self = this, pending = 0; Object.keys(self._models).forEach(function (model) { pending++; self.client.collection.delete(model, function (err, result) { if (!err || result.code == 404) { self.collection(model).create({waitForSync: true}, function (err) { if (err) { return callback && callback(err); } else { collectionCreated(); } }); } }.bind(self)); }, self); var collectionCreated = function () { if (--pending == 0 && callback) { callback(); } } }; Arango.prototype.defineForeignKey = function (model, key, cb) { cb(null, String); }; Arango.prototype.fromDatabase = function (model, data) { if (!data) { return null; } var props = this._models[model].properties; Object.keys(data).forEach(function (key) { if (/^_/.test(key)) { delete data[key]; return; } var val = data[key]; if (props[key]) { if (props[key].type.name === 'Date' && val !== null) { val = new Date(val); } } data[key] = val; }); return data; }; Arango.prototype.create = function (model, data, callback) { this.save(model, data, callback); }; Arango.prototype.save = function (model, data, callback) { var self = this; self.collection(model).save(data, function (err, res) { if (err) { return callback && callback(err); } else { data.id = parseInt(res._key); self.collection(model).update(res, {id: data.id}, function (err, res) { return callback && callback(err, data.id, res._rev); }); } }.bind(this)); }; Arango.prototype.findById = function (model, id, callback) { var self = this; self.collection(model).document('' + id, function (err, res) { var data; err = (err || {}).code !== 404 ? err : null; if (!err) { if (res && res.id) { res.id = parseInt(res._key); data = self.fromDatabase(model, res); } } return callback && callback(err, data); }.bind(this)); }; Arango.prototype.exists = function (model, id, callback) { this.collection(model).document('' + id, function (err, res) { return callback && callback(err, !err && res.id); }.bind(this)); }; Arango.prototype.all = Arango.prototype.find = function (model, filter, callback) { var self = this, query = ['FOR x IN @@collection']; var bindVars = { '@collection': model }; if (filter) { if (filter.where && Object.keys(filter.where).length === 0) { var csql = self.buildWhere(filter.where, self, model); query.push(csql.query.join(' ')); Object.keys(csql.bindVars).forEach(function (bkey) { bindVars[bkey] = csql.bindVars[bkey]; }); } if (filter.order) { var keys = filter.order; if (typeof keys === 'string') { keys = keys.split(','); } var args = {}; for (var index in keys) { var m = keys[index].match(/\s+(A|DE)SC$/); var key = keys[index]; key = key.replace(/\s+(A|DE)SC$/, '').trim(); if (m && m[1] === 'DE') { args[key] = 'DESC'; } else { args[key] = 'ASC'; } } var order = ''; Object.keys(args).forEach(function (kx) { order += 'x.`' + kx + '` ' + args[kx]; }); query.push('SORT ' + order); } if (filter.limit) { query.push('LIMIT @skip, @limit'); bindVars['skip'] = filter.skip || 0; bindVars['limit'] = filter.limit || 20; } query.push('RETURN x'); var maql = self.aqlQuery([query.join(' ')]); maql.bindVars = bindVars; if (!Object.keys(bindVars).length) { maql = query.join(' '); } self.client.query(maql, function (err, cursor) { if (err) { console.log('query err:', err.message); } var data = (cursor || {})._result || []; if (data && data.length) { data = data.map(function (i) { return self.fromDatabase(model, i); }); } return callback && callback(err, data) }.bind(self)); } else { var opt = {}; if (filter.limit) { opt.skip = filter.offset || filter.skip || 0; opt.limit = filter.limit; } self.collection(model).all(opt, function (err, res) { var data = (res || {})._result || []; if (data && data.length) { data = data.map(function (i) { return self.fromDatabase(model, i); }); } return callback && callback(err, data); }.bind(self)); } }; Arango.prototype.destroy = function (model, id, callback) { return this.collection(model).remove('' + id, function (err, res) { return callback && callback(err); }.bind(this)); }; Arango.prototype.updateAttributes = function (model, id, newData, callback) { this.collection(model).update('' + id, newData, function (err, data) { return callback && callback(err, data); }.bind(this)); }; // TODO: implement Arango.prototype.count = function (model, callback, where) { if (!where) { this.collection(model).count(function (err, res) { return callback && callback(err, (res || {}).count || 0); }.bind(this)); } else { this.collection(model).count(function (err, res) { return callback && callback(err, (res || {}).count || 0); }.bind(this)); } }; /** * Update rows * @param {String} model * @param {Object} filter * @param {Object} data * @param {Function} callback */ Arango.prototype.update = function (model, filter, data, callback) { if ('function' === typeof filter) { return filter(new Error("Get parametrs undefined"), null); } if ('function' === typeof data) { return data(new Error("Set parametrs undefined"), null); } filter = filter || {}; filter = filter.where ? filter.where : filter; this.collection(model).updateByExample(filter, data, callback); }; // TODO: implement Arango.prototype.remove = function remove(model, filter, callback) { // var cond = buildWhere(filter.where); this.collection(model).removeByExample(filter.where, callback); }; /** * Truncate collection. * @param {Function} callback */ Arango.prototype.destroyAll = function (model, callback) { this.collection(model).truncate(function (res) { var err = (res || {}).error ? res : null; return callback && callback(err); }.bind(this)); }; /** * EnsureIndex in collection. * @param {String} model * @param {Array} fields * @param {Array} unique * @param {Function} callback * @returns {*} */ Arango.prototype.ensureIndex = function ensureIndex(model, fields, unique, callback) { if (!fields || !fields.length) { return callback && callback(); } if (typeof unique === 'function') { callback = unique; unique = false; } this.collection(model) .createHashIndex(fields, {unique: unique}, callback); }; Arango.prototype.buildWhere = function buildWhere(conds, adapter, model) { 'use strict'; var qw = {}, cs = [], or = [], bindVars = {}, cix = 0, self = adapter, props = self._models[model].properties; Object.keys(conds).forEach(function (key) { if (key !== 'or') { qw = parseCond(cs, bindVars, key, props, conds, self, cix); } else { conds[key].forEach(function (oconds) { Object.keys(oconds).forEach(function (okey) { or = parseCond(or, bindVars, okey, props, oconds, self, cix); }); }); } cix++; }); if (cs.length === 0 && or.length === 0) { return ''; } var orop = ''; if (or.length) { orop = ' (' + or.join(' OR ') + ') '; } orop += (orop !== "" && cs.length > 0) ? ' AND ' : ''; return qw; }; Arango.prototype.toDatabase = function (prop, val, esc) { "use strict"; if (val === null) { return ''; } if (!prop) { return val; } if (val.constructor.name === 'Object' && type !== 'json') { var operator = Object.keys(val)[0]; val = val[operator]; if (operator === 'in' || operator === 'inq' || operator === 'nin') { if (!(val.propertyIsEnumerable('length')) && typeof val === 'object' && typeof val.length === 'number') { //if value is array for (var i = 0; i < val.length; i++) { val[i] = escape(val[i]); } return val.join(','); } else { return val; } } } if (prop.type.name === 'Number') { return val; } if (prop.type.name === 'Date') { if (!val) { return 0; } if (typeof val === 'string') { val = Date.parse(val); } if (val instanceof Date) { val = val.getTime(); } return val; } if (prop.type.name === "Boolean") { return val ? 1 : 0; } return esc ? escape(val) : val; }; function escape(val) { if (typeof val === 'string') { return '\'' + val + '\''; } return val; } var parseCond = function (cs, bindVars, key, props, conds, self, cix) { 'use strict'; var keyEscaped = 'FILTER x.`' + key + '`'; var val = conds[key]; if (val === null) { cs.push(keyEscaped + '\'\''); } else if (val.constructor.name === 'Object') { Object.keys(val).forEach(function (condType) { var inq = 'in,inq,nin'.indexOf(condType) > -1 ? 1 : 0; val = self.toDatabase(props[key], val[condType], true); var sqlCond = keyEscaped; if ((condType === 'inq' || condType === 'nin') && val.length === 0) { cs.push(condType === 'inq' ? 0 : 1); return true; } switch (condType.toString().toLowerCase()) { case 'gt': sqlCond += ' > '; break; case 'gte': sqlCond += ' >= '; break; case 'lt': sqlCond += ' < '; break; case 'lte': sqlCond += ' <= '; break; case 'between': sqlCond += ' '; break; case 'inq': case 'in': sqlCond += ' IN '; break; case 'nin': sqlCond += ' NOT IN '; break; case 'neq': case 'ne': sqlCond += ' != '; break; case 'regex': sqlCond += ' REGEXP '; break; case 'like': sqlCond += ' LIKE '; break; case 'nlike': sqlCond += ' NOT LIKE '; break; default: sqlCond += ' ' + condType + ' '; break; } if (condType === 'between') { sqlCond += ' >= @whereL'; sqlCond += ' AND x.`' + key + '`'; sqlCond += ' <= @whereG'; bindVars['whereL'] = val[0]; bindVars['whereG'] = val[1]; } else if (inq === 1) { sqlCond += ' @where' + cix; bindVars['where' + cix] = val; } else { sqlCond += val; } cs.push(sqlCond); }); } else if (/^\//gi.test(conds[key])) { var reg = val.toString().split('/'); cs.push(keyEscaped + ' REGEXP "' + reg[1] + '"'); } else { val = self.toDatabase(props[key], val, false); cs.push(keyEscaped + ' == @where' + cix); bindVars['where' + cix] = val; } return { query: cs, bindVars: bindVars }; };