From a62aab180dbb8d32fb45654027d250df2e459f09 Mon Sep 17 00:00:00 2001 From: Anatoliy Chakkaev Date: Fri, 14 Dec 2012 01:50:02 +0400 Subject: [PATCH] Removed nosql adapters (moved to own repos) --- index.js | 2 + lib/adapters/mongodb.js | 242 ---------------- lib/adapters/mongoose.js | 246 ---------------- lib/adapters/nano.js | 318 -------------------- lib/adapters/redis.js | 458 ----------------------------- lib/adapters/redis2.js | 556 ----------------------------------- lib/schema.js | 7 +- package.json | 4 +- test/common_test.js | 607 ++++++++++++++++++--------------------- 9 files changed, 296 insertions(+), 2144 deletions(-) delete mode 100644 lib/adapters/mongodb.js delete mode 100644 lib/adapters/mongoose.js delete mode 100644 lib/adapters/nano.js delete mode 100644 lib/adapters/redis.js delete mode 100644 lib/adapters/redis2.js diff --git a/index.js b/index.js index 37e7b46c..1c85a07c 100644 --- a/index.js +++ b/index.js @@ -22,3 +22,5 @@ try { } } catch (e) {} +exports.test = require('./test/common_test'); + diff --git a/lib/adapters/mongodb.js b/lib/adapters/mongodb.js deleted file mode 100644 index 333ad71a..00000000 --- a/lib/adapters/mongodb.js +++ /dev/null @@ -1,242 +0,0 @@ - -var safeRequire = require('../utils').safeRequire; - -/** - * Module dependencies - */ -var mongodb = safeRequire('mongodb'); -var ObjectID = mongodb.ObjectID; - -exports.initialize = function initializeSchema(schema, callback) { - if (!mongodb) return; - - var s = schema.settings; - - if (schema.settings.rs) { - - s.rs = schema.settings.rs; - if (schema.settings.url) { - var uris = schema.settings.url.split(','); - s.hosts = [] - s.ports = [] - uris.forEach(function(uri) { - var url = require('url').parse(uri); - - s.hosts.push(url.hostname || 'localhost'); - s.ports.push(parseInt(url.port || '27017', 10)); - - if (!s.database) s.database = url.pathname.replace(/^\//, ''); - if (!s.username) s.username = url.auth && url.auth.split(':')[0]; - if (!s.password) s.password = url.auth && url.auth.split(':')[1]; - }); - } - - s.database = s.database || 'test'; - - } else { - - if (schema.settings.url) { - var url = require('url').parse(schema.settings.url); - s.host = url.hostname; - s.port = url.port; - s.database = url.pathname.replace(/^\//, ''); - s.username = url.auth && url.auth.split(':')[0]; - s.password = url.auth && url.auth.split(':')[1]; - } - - s.host = s.host || 'localhost'; - s.port = parseInt(s.port || '27017', 10); - s.database = s.database || 'test'; - - } - - s.safe = s.safe || false; - - schema.adapter = new MongoDB(s, schema, callback); -}; - -function MongoDB(s, schema, callback) { - var i, n; - this._models = {}; - this.collections = {}; - - var server; - if (s.rs) { - set = []; - for (i = 0, n = s.hosts.length; i < n; i++) { - set.push(new mongodb.Server(s.hosts[i], s.ports[i], {auto_reconnect: true})); - } - server = new mongodb.ReplSetServers(set, {rs_name: s.rs}); - - } else { - server = new mongodb.Server(s.host, s.port, {}); - } - - new mongodb.Db(s.database, server, { safe: s.safe }).open(function (err, client) { - if (err) throw err; - if (s.username && s.password) { - t = this; - client.authenticate(s.username, s.password, function (err, result) { - t.client = client; - schema.client = client; - callback(); - }); - - } else { - this.client = client; - schema.client = client; - callback(); - } - }.bind(this)); -} - -MongoDB.prototype.define = function (descr) { - if (!descr.settings) descr.settings = {}; - this._models[descr.model.modelName] = descr; -}; - -MongoDB.prototype.defineProperty = function (model, prop, params) { - this._models[model].properties[prop] = params; -}; - -MongoDB.prototype.collection = function (name) { - if (!this.collections[name]) { - this.collections[name] = new mongodb.Collection(this.client, name); - } - return this.collections[name]; -}; - -MongoDB.prototype.create = function (model, data, callback) { - if (data.id === null) { - delete data.id; - } - this.collection(model).insert(data, {}, function (err, m) { - callback(err, err ? null : m[0]._id.toString()); - }); -}; - -MongoDB.prototype.save = function (model, data, callback) { - this.collection(model).update({_id: new ObjectID(data.id)}, data, function (err) { - callback(err); - }); -}; - -MongoDB.prototype.exists = function (model, id, callback) { - this.collection(model).findOne({_id: new ObjectID(id)}, function (err, data) { - callback(err, !err && data); - }); -}; - -MongoDB.prototype.find = function find(model, id, callback) { - this.collection(model).findOne({_id: new ObjectID(id)}, function (err, data) { - if (data) data.id = id; - callback(err, data); - }); -}; - -MongoDB.prototype.updateOrCreate = function updateOrCreate(model, data, callback) { - var adapter = this; - if (!data.id) return this.create(data, callback); - this.find(model, data.id, function (err, inst) { - if (err) return callback(err); - if (inst) { - adapter.updateAttributes(model, data.id, data, callback); - } else { - delete data.id; - adapter.create(model, data, function (err, id) { - if (err) return callback(err); - if (id) { - data.id = id; - delete data._id; - callback(null, data); - } else{ - callback(null, null); // wtf? - } - }); - } - }); -}; - -MongoDB.prototype.destroy = function destroy(model, id, callback) { - this.collection(model).remove({_id: new ObjectID(id)}, callback); -}; - -MongoDB.prototype.all = function all(model, filter, callback) { - if (!filter) { - filter = {}; - } - var query = {}; - if (filter.where) { - Object.keys(filter.where).forEach(function (k) { - var cond = filter.where[k]; - var spec = false; - if (cond && cond.constructor.name === 'Object') { - spec = Object.keys(cond)[0]; - cond = cond[spec]; - } - if (spec) { - if (spec === 'between') { - query[k] = { $gte: cond[0], $lte: cond[1]}; - } else { - query[k] = {}; - query[k]['$' + spec] = cond; - } - } else { - if (cond === null) { - query[k] = {$type: 10}; - } else { - query[k] = cond; - } - } - }); - } - var cursor = this.collection(model).find(query); - - if (filter.order) { - var m = filter.order.match(/\s+(A|DE)SC$/); - var key = filter.order; - var reverse = false; - if (m) { - key = key.replace(/\s+(A|DE)SC$/, ''); - if (m[1] === 'DE') reverse = true; - } - if (reverse) { - cursor.sort([[key, 'desc']]); - } else { - cursor.sort(key); - } - } - if (filter.limit) { - cursor.limit(filter.limit); - } - if (filter.skip) { - cursor.skip(filter.skip); - } else if (filter.offset) { - cursor.skip(filter.offset); - } - cursor.toArray(function (err, data) { - if (err) return callback(err); - callback(null, data.map(function (o) { o.id = o._id.toString(); delete o._id; return o; })); - }); -}; - -MongoDB.prototype.destroyAll = function destroyAll(model, callback) { - this.collection(model).remove({}, callback); -}; - -MongoDB.prototype.count = function count(model, callback, where) { - this.collection(model).count(where, function (err, count) { - callback(err, count); - }); -}; - -MongoDB.prototype.updateAttributes = function updateAttrs(model, id, data, cb) { - this.collection(model).findAndModify({_id: new ObjectID(id)}, [['_id','asc']], {$set: data}, {}, function(err, object) { - cb(err, object); - }); -}; - -MongoDB.prototype.disconnect = function () { - this.client.close(); -}; - diff --git a/lib/adapters/mongoose.js b/lib/adapters/mongoose.js deleted file mode 100644 index 4c9cba6d..00000000 --- a/lib/adapters/mongoose.js +++ /dev/null @@ -1,246 +0,0 @@ -var safeRequire = require('../utils').safeRequire; - -/** - * Module dependencies - */ -var mongoose = safeRequire('mongoose'); - -exports.initialize = function initializeSchema(schema, callback) { - console.error('WARN: mongoose adapter is not supported, please use "mongodb" adapter instead'); - if (!mongoose) return; - - if (!schema.settings.url) { - var url = schema.settings.host || 'localhost'; - if (schema.settings.port) url += ':' + schema.settings.port; - var auth = ''; - if (schema.settings.username) { - auth = schema.settings.username; - if (schema.settings.password) { - auth += ':' + schema.settings.password; - } - } - if (auth) { - url = auth + '@' + url; - } - if (schema.settings.database) { - url += '/' + schema.settings.database; - } else { - url += '/'; - } - url = 'mongodb://' + url; - schema.settings.url = url; - } - if (!schema.settings.rs) { - schema.client = mongoose.connect(schema.settings.url); - } else { - schema.client = mongoose.connectSet(schema.settings.url, {rs_name: schema.settings.rs}); - } - - schema.adapter = new MongooseAdapter(schema.client); - process.nextTick(callback); -}; - -function MongooseAdapter(client) { - this._models = {}; - this.client = client; - this.cache = {}; -} - -MongooseAdapter.prototype.define = function (descr) { - var props = {}; - Object.keys(descr.properties).forEach(function (key) { - props[key] = {}; - props[key].type = descr.properties[key].type; - if (props[key].type.name === 'Text' || props[key].type.name === 'JSON') { - props[key].type = String; - } - if (props[key].type.name === 'Object') { - props[key].type = mongoose.Schema.Types.Mixed; - } - if (descr.properties[key].index) { - props[key].index = descr.properties[key].index; - } - }); - var schema = new mongoose.Schema(props); - this._models[descr.model.modelName] = mongoose.model(descr.model.modelName, schema, descr.settings.table || null); - this.cache[descr.model.modelName] = {}; -}; - -MongooseAdapter.prototype.defineForeignKey = function (model, key, cb) { - var piece = {}; - piece[key] = {type: mongoose.Schema.ObjectId, index: true}; - this._models[model].schema.add(piece); - cb(null, String); -}; - -MongooseAdapter.prototype.setCache = function (model, instance) { - this.cache[model][instance.id] = instance; -}; - -MongooseAdapter.prototype.getCached = function (model, id, cb) { - if (this.cache[model][id]) { - cb(null, this.cache[model][id]); - } else { - this._models[model].findById(id, function (err, instance) { - if (err) { - return cb(err); - } - this.cache[model][id] = instance; - cb(null, instance); - }.bind(this)); - } -}; - -MongooseAdapter.prototype.create = function (model, data, callback) { - var m = new this._models[model](data); - m.save(function (err) { - callback(err, err ? null : m.id); - }); -}; - -MongooseAdapter.prototype.save = function (model, data, callback) { - this.getCached(model, data.id, function (err, inst) { - if (err) { - return callback(err); - } - merge(inst, data); - inst.save(callback); - }); -}; - -MongooseAdapter.prototype.exists = function (model, id, callback) { - delete this.cache[model][id]; - this.getCached(model, id, function (err, data) { - if (err) { - return callback(err); - } - callback(err, !!data); - }); -}; - -MongooseAdapter.prototype.find = function find(model, id, callback) { - delete this.cache[model][id]; - this.getCached(model, id, function (err, data) { - if (err) { - return callback(err); - } - callback(err, data ? data.toObject() : null); - }); -}; - -MongooseAdapter.prototype.destroy = function destroy(model, id, callback) { - this.getCached(model, id, function (err, data) { - if (err) { - return callback(err); - } - if (data) { - data.remove(callback); - } else { - callback(null); - } - }); -}; - -MongooseAdapter.prototype.all = function all(model, filter, callback) { - if (!filter) { - filter = {}; - } - var query = this._models[model].find({}); - if (filter.where) { - Object.keys(filter.where).forEach(function (k) { - var cond = filter.where[k]; - var spec = false; - if (cond && cond.constructor.name === 'Object') { - spec = Object.keys(cond)[0]; - cond = cond[spec]; - } - if (spec) { - if (spec === 'between') { - query.where(k).gte(cond[0]).lte(cond[1]); - } else { - query.where(k)[spec](cond); - } - } else { - query.where(k, cond); - } - }); - } - if (filter.order) { - var keys = filter.order; // can be Array or String - if (typeof(keys) == "string") { - keys = keys.split(','); - } - var args = []; - - for(index in keys) { - var m = keys[index].match(/\s+(A|DE)SC$/); - - keys[index] = keys[index].replace(/\s+(A|DE)SC$/, ''); - if (m && m[1] === 'DE') { - query.sort(keys[index].trim(), -1); - } else { - query.sort(keys[index].trim(), 1); - } - } - } - if (filter.limit) { - query.limit(filter.limit); - } - if (filter.skip) { - query.skip(filter.skip); - } else if (filter.offset) { - query.skip(filter.offset); - } - query.exec(function (err, data) { - if (err) return callback(err); - callback(null, data); - }); -}; - -MongooseAdapter.prototype.destroyAll = function destroyAll(model, callback) { - var wait = 0; - this._models[model].find(function (err, data) { - if (err) return callback(err); - wait = data.length; - if (!data.length) return callback(null); - data.forEach(function (obj) { - obj.remove(done) - }); - }); - - var error = null; - function done(err) { - error = error || err; - if (--wait === 0) { - callback(error); - } - } - -}; - -MongooseAdapter.prototype.count = function count(model, callback, where) { - this._models[model].count(where || {}, callback); -}; - -MongooseAdapter.prototype.updateAttributes = function updateAttrs(model, id, data, cb) { - this.getCached(model, id, function (err, inst) { - if (err) { - return cb(err); - } else if (inst) { - merge(inst, data); - inst.save(cb); - } else cb(); - }); -}; - -MongooseAdapter.prototype.disconnect = function () { - this.client.connection.close(); -}; - -function merge(base, update) { - Object.keys(update).forEach(function (key) { - base[key] = update[key]; - }); - return base; -} - diff --git a/lib/adapters/nano.js b/lib/adapters/nano.js deleted file mode 100644 index cec6456d..00000000 --- a/lib/adapters/nano.js +++ /dev/null @@ -1,318 +0,0 @@ -// Generated by CoffeeScript 1.4.0 -(function() { - var NanoAdapter, helpers, _, - __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, - __slice = [].slice; - - _ = require('lodash')._; - - exports.initialize = function(schema, callback) { - var db, design, opts; - if (!(opts = schema.settings)) { - throw new Error('url is missing'); - } - db = require('nano')(opts); - schema.adapter = new NanoAdapter(db); - design = { - views: { - by_model: { - map: 'function (doc) { if (doc.model) return emit(doc.model, null); }' - } - } - }; - return db.insert(design, '_design/nano', function(err, doc) { - return callback(); - }); - }; - - NanoAdapter = (function() { - - function NanoAdapter(db) { - this.db = db; - this.all = __bind(this.all, this); - - this.fromDB = __bind(this.fromDB, this); - - this.forDB = __bind(this.forDB, this); - - this.destroyAll = __bind(this.destroyAll, this); - - this.count = __bind(this.count, this); - - this.updateAttributes = __bind(this.updateAttributes, this); - - this.destroy = __bind(this.destroy, this); - - this.find = __bind(this.find, this); - - this.exists = __bind(this.exists, this); - - this.updateOrCreate = __bind(this.updateOrCreate, this); - - this.save = __bind(this.save, this); - - this.create = __bind(this.create, this); - - this.define = __bind(this.define, this); - - this._models = {}; - } - - NanoAdapter.prototype.define = function(descr) { - var m; - m = descr.model.modelName; - descr.properties._rev = { - type: String - }; - return this._models[m] = descr; - }; - - NanoAdapter.prototype.create = function() { - var args; - args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; - return this.save.apply(this, args); - }; - - NanoAdapter.prototype.save = function(model, data, callback) { - var _this = this; - data.model = model; - helpers.savePrep(data); - return this.db.insert(this.forDB(model, data), function(err, doc) { - return callback(err, doc.id, doc.rev); - }); - }; - - NanoAdapter.prototype.updateOrCreate = function(model, data, callback) { - var _this = this; - return this.exists(model, data.id, function(err, exists) { - if (exists) { - return _this.save(model, data, callback); - } else { - return _this.create(model, data, function(err, id) { - data.id = id; - return callback(err, data); - }); - } - }); - }; - - NanoAdapter.prototype.exists = function(model, id, callback) { - return this.db.head(id, function(err, _, headers) { - if (err) { - return callback(null, false); - } - return callback(null, headers != null); - }); - }; - - NanoAdapter.prototype.find = function(model, id, callback) { - var _this = this; - return this.db.get(id, function(err, doc) { - return callback(err, _this.fromDB(model, doc)); - }); - }; - - NanoAdapter.prototype.destroy = function(model, id, callback) { - var _this = this; - return this.db.get(id, function(err, doc) { - if (err) { - return callback(err); - } - return _this.db.destroy(id, doc._rev, function(err, doc) { - if (err) { - return callback(err); - } - callback.removed = true; - return callback(); - }); - }); - }; - - NanoAdapter.prototype.updateAttributes = function(model, id, data, callback) { - var _this = this; - return this.db.get(id, function(err, base) { - if (err) { - return callback(err); - } - return _this.save(model, helpers.merge(base, data), callback); - }); - }; - - NanoAdapter.prototype.count = function(model, callback, where) { - var _this = this; - return this.all(model, { - where: where - }, function(err, docs) { - return callback(err, docs.length); - }); - }; - - NanoAdapter.prototype.destroyAll = function(model, callback) { - var _this = this; - return this.all(model, {}, function(err, docs) { - var doc; - docs = (function() { - var _i, _len, _results; - _results = []; - for (_i = 0, _len = docs.length; _i < _len; _i++) { - doc = docs[_i]; - _results.push({ - _id: doc.id, - _rev: doc._rev, - _deleted: true - }); - } - return _results; - })(); - return _this.db.bulk({ - docs: docs - }, function(err, body) { - return callback(err, body); - }); - }); - }; - - NanoAdapter.prototype.forDB = function(model, data) { - var k, props, v; - if (data == null) { - data = {}; - } - props = this._models[model].properties; - for (k in props) { - v = props[k]; - if (data[k] && props[k].type.name === 'Date' && (data[k].getTime != null)) { - data[k] = data[k].getTime(); - } - } - return data; - }; - - NanoAdapter.prototype.fromDB = function(model, data) { - var date, k, props, v; - if (!data) { - return data; - } - props = this._models[model].properties; - for (k in props) { - v = props[k]; - if ((data[k] != null) && props[k].type.name === 'Date') { - date = new Date(data[k]); - date.setTime(data[k]); - data[k] = date; - } - } - return data; - }; - - NanoAdapter.prototype.all = function(model, filter, callback) { - var params, - _this = this; - params = { - keys: [model], - include_docs: true - }; - return this.db.view('nano', 'by_model', params, function(err, body) { - var doc, docs, i, k, key, orders, row, sorting, v, where, _i, _len; - docs = (function() { - var _i, _len, _ref, _results; - _ref = body.rows; - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - row = _ref[_i]; - row.doc.id = row.doc._id; - delete row.doc._id; - _results.push(row.doc); - } - return _results; - })(); - if (where = filter != null ? filter.where : void 0) { - for (k in where) { - v = where[k]; - if (_.isDate(v)) { - where[k] = v.getTime(); - } - } - docs = _.where(docs, where); - } - if (orders = filter != null ? filter.order : void 0) { - if (_.isString(orders)) { - orders = [orders]; - } - sorting = function(a, b) { - var ak, bk, i, item, rev, _i, _len; - for (i = _i = 0, _len = this.length; _i < _len; i = ++_i) { - item = this[i]; - ak = a[this[i].key]; - bk = b[this[i].key]; - rev = this[i].reverse; - if (ak > bk) { - return 1 * rev; - } - if (ak < bk) { - return -1 * rev; - } - } - return 0; - }; - for (i = _i = 0, _len = orders.length; _i < _len; i = ++_i) { - key = orders[i]; - orders[i] = { - reverse: helpers.reverse(key), - key: helpers.stripOrder(key) - }; - } - docs.sort(sorting.bind(orders)); - } - return callback(err, (function() { - var _j, _len1, _results; - _results = []; - for (_j = 0, _len1 = docs.length; _j < _len1; _j++) { - doc = docs[_j]; - _results.push(this.fromDB(model, doc)); - } - return _results; - }).call(_this)); - }); - }; - - return NanoAdapter; - - })(); - - helpers = { - merge: function(base, update) { - var k, v; - if (!base) { - return update; - } - for (k in update) { - v = update[k]; - base[k] = update[k]; - } - return base; - }, - reverse: function(key) { - var hasOrder; - if (hasOrder = key.match(/\s+(A|DE)SC$/i)) { - if (hasOrder[1] === "DE") { - return -1; - } - } - return 1; - }, - stripOrder: function(key) { - return key.replace(/\s+(A|DE)SC/i, ""); - }, - savePrep: function(data) { - var id; - if (id = data.id) { - delete data.id; - data._id = id.toString(); - } - if (data._rev === null) { - return delete data._rev; - } - } - }; - -}).call(this); diff --git a/lib/adapters/redis.js b/lib/adapters/redis.js deleted file mode 100644 index 2130ce48..00000000 --- a/lib/adapters/redis.js +++ /dev/null @@ -1,458 +0,0 @@ -var safeRequire = require('../utils').safeRequire; - -/** - * Module dependencies - */ -var redis = safeRequire('redis'); - -exports.initialize = function initializeSchema(schema, callback) { - console.log('GOOD NEWS! This redis adapter version is deprecated, use redis2 instead. A lot of improvements, and new indexes incompatible with old (sorry about that): now we only store id and not ModelName:id in indexes. Also dates format in indexes changed to unix timestamp for better sorting and filtering performance'); - if (!redis) return; - - if (schema.settings.url) { - var url = require('url'); - var redisUrl = url.parse(schema.settings.url); - var redisAuth = (redisUrl.auth || '').split(':'); - schema.settings.host = redisUrl.hostname; - schema.settings.port = redisUrl.port; - - if (redisAuth.length == 2) { - schema.settings.db = redisAuth[0]; - schema.settings.password = redisAuth[1]; - } - } - - schema.client = redis.createClient( - schema.settings.port, - schema.settings.host, - schema.settings.options - ); - schema.client.auth(schema.settings.password); - var callbackCalled = false; - var database = schema.settings.hasOwnProperty('database') && schema.settings.database; - schema.client.on('connect', function () { - if (!callbackCalled && database === false) { - callbackCalled = true; - callback(); - } else if (database !== false) { - if (callbackCalled) { - return schema.client.select(schema.settings.database); - } else { - callbackCalled = true; - return schema.client.select(schema.settings.database, callback); - } - } - }); - - schema.adapter = new BridgeToRedis(schema.client); -}; - -function BridgeToRedis(client) { - this._models = {}; - this.client = client; - this.indexes = {}; -} - -BridgeToRedis.prototype.define = function (descr) { - var m = descr.model.modelName; - this._models[m] = descr; - this.indexes[m] = {}; - Object.keys(descr.properties).forEach(function (prop) { - if (descr.properties[prop].index) { - this.indexes[m][prop] = descr.properties[prop].type; - } - }.bind(this)); -}; - -BridgeToRedis.prototype.defineForeignKey = function (model, key, cb) { - this.indexes[model][key] = Number; - cb(null, Number); -}; - -BridgeToRedis.prototype.save = function (model, data, callback) { - deleteNulls(data); - var log = this.logger('HMSET ' + model + ':' + data.id + ' ...'); - this.client.hmset(model + ':' + data.id, data, function (err) { - log(); - if (err) return callback(err); - this.updateIndexes(model, data.id, data, callback); - }.bind(this)); -}; - -BridgeToRedis.prototype.updateIndexes = function (model, id, data, callback) { - var i = this.indexes[model]; - var schedule = [['sadd', 's:' + model, id]]; - Object.keys(data).forEach(function (key) { - if (i[key]) { - schedule.push([ - 'sadd', - 'i:' + model + ':' + key + ':' + data[key], - model + ':' + id - ]); - } - }.bind(this)); - - if (schedule.length) { - this.client.multi(schedule).exec(function (err) { - callback(err, data); - }); - } else { - callback(null); - } -}; - -BridgeToRedis.prototype.create = function (model, data, callback) { - if (data.id) return create.call(this, data.id, true); - - var log = this.logger('INCR id:' + model); - this.client.incr('id:' + model, function (err, id) { - log(); - create.call(this, id); - }.bind(this)); - - function create(id, upsert) { - data.id = id; - this.save(model, data, function (err) { - if (callback) { - callback(err, id); - } - }); - - // push the id to the list of user ids for sorting - log('SADD s:' + model + ' ' + data.id); - this.client.sadd("s:" + model, upsert ? data : data.id); - } -}; - -BridgeToRedis.prototype.updateOrCreate = function (model, data, callback) { - if (!data.id) return this.create(model, data, callback); - this.save(model, data, callback); -}; - -BridgeToRedis.prototype.exists = function (model, id, callback) { - var log = this.logger('EXISTS ' + model + ':' + id); - this.client.exists(model + ':' + id, function (err, exists) { - log(); - if (callback) { - callback(err, exists); - } - }); -}; - -BridgeToRedis.prototype.find = function find(model, id, callback) { - var t1 = Date.now(); - this.client.hgetall(model + ':' + id, function (err, data) { - this.log('HGETALL ' + model + ':' + id, t1); - if (data && Object.keys(data).length > 0) { - data.id = id; - } else { - data = null; - } - callback(err, data); - }.bind(this)); -}; - -BridgeToRedis.prototype.destroy = function destroy(model, id, callback) { - var t1 = Date.now(); - this.client.del(model + ':' + id, function (err) { - this.log('DEL ' + model + ':' + id, t1); - callback(err); - }.bind(this)); - this.log('SREM s:' + model, t1); - this.client.srem("s:" + model, id); -}; - -BridgeToRedis.prototype.possibleIndexes = function (model, filter) { - if (!filter || Object.keys(filter.where || {}).length === 0) return false; - - var foundIndex = []; - var noIndex = []; - Object.keys(filter.where).forEach(function (key) { - if (this.indexes[model][key] && (typeof filter.where[key] === 'string' || typeof filter.where[key] === 'number')) { - foundIndex.push('i:' + model + ':' + key + ':' + filter.where[key]); - } else { - noIndex.push(key); - } - }.bind(this)); - - return [foundIndex, noIndex]; -}; - -BridgeToRedis.prototype.all = function all(model, filter, callback) { - var ts = Date.now(); - var client = this.client; - var log = this.log; - var t1 = Date.now(); - var cmd; - var that = this; - var sortCmd = []; - var props = this._models[model].properties; - var allNumeric = true; - - // TODO: we need strict mode when filtration only possible when we have indexes - // WHERE - if (filter && filter.where) { - var pi = this.possibleIndexes(model, filter); - var indexes = pi[0]; - var noIndexes = pi[1]; - - if (indexes && indexes.length) { - cmd = 'SINTER "' + indexes.join('" "') + '"'; - if (noIndexes.length) { - log(model + ': no indexes found for ', noIndexes.join(', '), - 'slow sorting and filtering'); - } - indexes.push(noIndexes.length ? orderLimitStageBad : orderLimitStage); - client.sinter.apply(client, indexes); - } else { - // filter manually - cmd = 'KEYS ' + model + ':*'; - client.keys(model + ':*', orderLimitStageBad); - } - } else { - // no filtering, just sort/limit (if any) - gotKeys('*'); - } - - // bad case when we trying to filter on non-indexed fields - // in bad case we need retrieve all data and filter/limit/sort manually - function orderLimitStageBad(err, keys) { - log(cmd, t1); - var t2 = Date.now(); - if (err) { - return callback(err, []); - } - var query = keys.map(function (key) { - return ['hgetall', key]; - }); - client.multi(query).exec(function (err, replies) { - log(query, t2); - gotFilteredData(err, replies.filter(applyFilter(filter))); - }); - - function gotFilteredData(err, nodes) { - if (err) return callback(null); - - if (filter.order) { - var allNumeric = true; - var orders = filter.order; - if (typeof filter.order === "string") { - orders = [filter.order]; - } - orders.forEach(function (key) { - key = key.split(' ')[0]; - if (props[key].type.name !== 'Number' && props[key].type.name !== 'Date') { - allNumeric = false; - } - }); - if (allNumeric) { - nodes = nodes.sort(numerically.bind(orders)); - } else { - nodes = nodes.sort(literally.bind(orders)); - } - } - - // LIMIT - if (filter && filter.limit) { - var from = (filter.offset || 0), to = from + filter.limit; - callback(null, nodes.slice(from, to)); - } else { - callback(null, nodes); - } - } - } - - function orderLimitStage(err, keys) { - log(cmd, t1); - var t2 = Date.now(); - if (err) { - return callback(err, []); - } - - gotKeys(keys); - } - - function gotKeys(keys) { - - // ORDER - var reverse = false; - if (filter && filter.order) { - var orders = filter.order; - if (typeof filter.order === "string"){ - orders = [filter.order]; - } - orders.forEach(function (key) { - var m = key.match(/\s+(A|DE)SC$/i); - if (m) { - key = key.replace(/\s+(A|DE)SC/i, ''); - if (m[1] === 'DE') reverse = true; - } - if (key !== 'id') { - if (props[key].type.name !== 'Number' && props[key].type.name !== 'Date') { - allNumeric = false; - } - } - sortCmd.push("BY", model + ":*->" + key); - }); - } - - // LIMIT - if (keys === '*' && filter && filter.limit){ - var from = (filter.offset || 0), to = from + filter.limit; - sortCmd.push("LIMIT", from, to); - } - - // we need ALPHA modifier when sorting string values - // the only case it's not required - we sort numbers - // TODO: check if we sort numbers - if (!allNumeric) { - sortCmd.push('ALPHA'); - } - - if (reverse) { - sortCmd.push('DESC'); - } - - if (sortCmd.length) { - sortCmd.unshift("s:" + model); - sortCmd.push("GET", "#"); - cmd = "SORT " + sortCmd.join(" "); - var ttt = Date.now(); - sortCmd.push(function(err, ids){ - if (err) { - return callback(err, []); - } - log(cmd, ttt); - var sortedKeys = ids.map(function (i) { - return model + ":" + i; - }); - handleKeys(err, intersect(sortedKeys, keys)); - }); - client.sort.apply(client, sortCmd); - } else { - // no sorting or filtering: just get all keys - if (keys === '*') { - cmd = 'KEYS ' + model + ':*'; - client.keys(model + ':*', handleKeys); - } else { - handleKeys(null, keys); - } - } - } - - function handleKeys(err, keys) { - var t2 = Date.now(); - var query = keys.map(function (key) { - return ['hgetall', key]; - }); - client.multi(query).exec(function (err, replies) { - log(query, t2); - // console.log('Redis time: %dms', Date.now() - ts); - callback(err, filter ? replies.filter(applyFilter(filter)) : replies); - }); - } - - return; - - function numerically(a, b) { - return a[this[0]] - b[this[0]]; - } - - function literally(a, b) { - return a[this[0]] > b[this[0]]; - } - - // TODO: find better intersection method - function intersect(sortedKeys, filteredKeys) { - if (filteredKeys === '*') return sortedKeys; - var index = {}; - filteredKeys.forEach(function (x) { - index[x] = true; - }); - return sortedKeys.filter(function (x) { - return index[x]; - }); - } -}; - -function applyFilter(filter) { - if (typeof filter.where === 'function') { - return filter.where; - } - var keys = Object.keys(filter.where || {}); - return function (obj) { - var pass = true; - if (!obj) return false; - keys.forEach(function (key) { - if (!test(filter.where[key], obj[key])) { - pass = false; - } - }); - return pass; - }; - - function test(example, value) { - if (typeof value === 'string' && example && example.constructor.name === 'RegExp') { - return value.match(example); - } - // not strict equality - return example == value; - } -} - -BridgeToRedis.prototype.destroyAll = function destroyAll(model, callback) { - var keysQuery = model + ':*'; - var t1 = Date.now(); - this.client.keys(keysQuery, function (err, keys) { - this.log('KEYS ' + keysQuery, t1); - if (err) { - return callback(err, []); - } - var query = keys.map(function (key) { - return ['del', key]; - }); - var t2 = Date.now(); - this.client.multi(query).exec(function (err, replies) { - this.log(query, t2); - this.client.del('s:' + model, function () { - callback(err); - }); - }.bind(this)); - }.bind(this)); -}; - -BridgeToRedis.prototype.count = function count(model, callback, where) { - var keysQuery = model + ':*'; - var t1 = Date.now(); - if (where && Object.keys(where).length) { - this.all(model, {where: where}, function (err, data) { - callback(err, err ? null : data.length); - }); - } else { - this.client.keys(keysQuery, function (err, keys) { - this.log('KEYS ' + keysQuery, t1); - callback(err, err ? null : keys.length); - }.bind(this)); - } -}; - -BridgeToRedis.prototype.updateAttributes = function updateAttrs(model, id, data, cb) { - var t1 = Date.now(); - deleteNulls(data); - this.client.hmset(model + ':' + id, data, function () { - this.log('HMSET ' + model + ':' + id, t1); - this.updateIndexes(model, id, data, cb); - }.bind(this)); -}; - -function deleteNulls(data) { - Object.keys(data).forEach(function (key) { - if (data[key] === null) delete data[key]; - }); -} - -BridgeToRedis.prototype.disconnect = function disconnect() { - this.log('QUIT', Date.now()); - this.client.quit(); -}; - diff --git a/lib/adapters/redis2.js b/lib/adapters/redis2.js deleted file mode 100644 index ab8ff0ab..00000000 --- a/lib/adapters/redis2.js +++ /dev/null @@ -1,556 +0,0 @@ -var safeRequire = require('../utils').safeRequire; - -/** - * Module dependencies - */ -var redis = safeRequire('redis'); - -exports.initialize = function initializeSchema(schema, callback) { - if (!redis) return; - - if (schema.settings.url) { - var url = require('url'); - var redisUrl = url.parse(schema.settings.url); - var redisAuth = (redisUrl.auth || '').split(':'); - schema.settings.host = redisUrl.hostname; - schema.settings.port = redisUrl.port; - - if (redisAuth.length == 2) { - schema.settings.db = redisAuth[0]; - schema.settings.password = redisAuth[1]; - } - } - - schema.client = redis.createClient( - schema.settings.port, - schema.settings.host, - schema.settings.options - ); - schema.client.auth(schema.settings.password); - var callbackCalled = false; - var database = schema.settings.hasOwnProperty('database') && schema.settings.database; - schema.client.on('connect', function () { - if (!callbackCalled && database === false) { - callbackCalled = true; - callback(); - } else if (database !== false) { - if (callbackCalled) { - return schema.client.select(schema.settings.database); - } else { - callbackCalled = true; - return schema.client.select(schema.settings.database, callback); - } - } - }); - schema.client.on('error', function (error) { - console.log(error); - }) - - var clientWrapper = new Client(schema.client); - - schema.adapter = new BridgeToRedis(clientWrapper); - clientWrapper._adapter = schema.adapter; -}; - -function Client(client) { - this._client = client; -} - -var commands = Object.keys(redis.Multi.prototype).filter(function (n) { - return n.match(/^[a-z]/); -}); - -commands.forEach(function (cmd) { - - Client.prototype[cmd] = function (args, callback) { - - var c = this._client, log; - - if (typeof args === 'string') { - args = [args]; - } - - if (!args) args = []; - - log = this._adapter.logger(cmd.toUpperCase() + ' ' + args.map(function (a) { - if (typeof a === 'object') return JSON.stringify(a); - return a; - }).join(' ')); - args.push(function (err, res) { - if (err) console.log(err); - log(); - if (callback) { - callback(err, res); - } - }); - - c[cmd].apply(c, args); - }; -}); - -Client.prototype.multi = function (commands, callback) { - if (commands.length === 0) return callback && callback(); - if (commands.length === 1) { - return this[commands[0].shift().toLowerCase()].call( - this, - commands[0], - callback && function (e, r) { callback(e, [r]) }); - } - var log = this._adapter.logger('MULTI\n ' + commands.map(function (x) { - return x.join(' '); - }).join('\n ') + '\nEXEC'); - this._client.multi(commands).exec(function (err, replies) { - if (err) console.log(err); - log(); - callback && callback(err, replies); - }); -}; - -Client.prototype.transaction = function () { - return new Transaction(this); -}; - -function Transaction(client) { - this._client = client; - this._handlers = []; - this._schedule = []; -} - -Transaction.prototype.run = function (cb) { - var t = this; - var atLeastOneHandler = false; - switch (this._schedule.length) { - case 0: return cb(); - case 1: return this._client[this._schedule[0].shift()].call( - this._client, - this._schedule[0], - this._handlers[0] || cb); - default: - this._client.multi(this._schedule, function (err, replies) { - if (err) return cb(err); - replies.forEach(function (r, i) { - if (t._handlers[i]) { - atLeastOneHandler = true; - t._handlers[i](err, r); - } - }); - if (!atLeastOneHandler) cb(err); - }); - } - -}; - -commands.forEach(function (k) { - - Transaction.prototype[k] = function (args, cb) { - if (typeof args === 'string') { - args = [args]; - } - args.unshift(k); - this._schedule.push(args); - this._handlers.push(cb || false); - }; - -}); - -function BridgeToRedis(client) { - this._models = {}; - this.client = client; - this.indexes = {}; -} - -BridgeToRedis.prototype.define = function (descr) { - var m = descr.model.modelName; - this._models[m] = descr; - this.indexes[m] = {}; - Object.keys(descr.properties).forEach(function (prop) { - if (descr.properties[prop].index) { - this.indexes[m][prop] = descr.properties[prop].type; - } - }.bind(this)); -}; - -BridgeToRedis.prototype.defineForeignKey = function (model, key, cb) { - this.indexes[model][key] = Number; - cb(null, Number); -}; - -BridgeToRedis.prototype.forDb = function (model, data) { - var p = this._models[model].properties; - for (var i in data) { - if (!p[i]) continue; - if (!data[i]) { - data[i] = ""; - continue; - } - switch (p[i].type.name) { - case "Date": - data[i] = data[i].getTime ? data[i].getTime().toString() : "0"; - break; - case "Number": - data[i] = data[i].toString(); - break; - case "Boolean": - data[i] = !!data[i] ? "1" : "0"; - break; - case "String": - case "Text": - break; - default: - data[i] = JSON.stringify(data[i]); - } - } - return data; -}; - -BridgeToRedis.prototype.fromDb = function (model, data) { - var p = this._models[model].properties, d; - for (var i in data) { - if (!p[i]) continue; - if (!data[i]) { - data[i] = ""; - continue; - } - switch (p[i].type.name) { - case "Date": - d = new Date(data[i]); - d.setTime(data[i]); - data[i] = d; - break; - case "Number": - data[i] = Number(data[i]); - break; - case "Boolean": - data[i] = data[i] === "1"; - break; - default: - d = data[i]; - try { - data[i] = JSON.parse(data[i]); - } - catch(e) { - data[i] = d; - } - } - } - return data; -}; - -BridgeToRedis.prototype.save = function (model, data, callback) { - data = this.forDb(model, data); - deleteNulls(data); - this.client.hgetall(model + ':' + data.id, function (err, prevData) { - if (err) return callback(err); - this.client.hmset([model + ':' + data.id, data], function (err) { - if (err) return callback(err); - if (prevData) { - Object.keys(prevData).forEach(function (k) { - if (data.hasOwnProperty(k)) return; - data[k] = prevData[k]; - }); - } - this.updateIndexes(model, data.id, data, callback, this.forDb(model, prevData)); - }.bind(this)); - }.bind(this)); -}; - -BridgeToRedis.prototype.updateIndexes = function (model, id, data, callback, prevData) { - var p = this._models[model].properties; - var i = this.indexes[model]; - var schedule = []; - if (!callback.removed) { - schedule.push(['SADD', 's:' + model, id]); - } - Object.keys(i).forEach(function (key) { - if (data.hasOwnProperty(key)) { - var val = data[key]; - schedule.push([ - 'SADD', - 'i:' + model + ':' + key + ':' + val, - id - ]); - } - if (prevData && prevData[key] !== data[key]) { - schedule.push([ - 'SREM', - 'i:' + model + ':' + key + ':' + prevData[key], - id - ]); - } - }); - - if (schedule.length) { - this.client.multi(schedule, function (err) { - callback(err, data); - }); - } else { - callback(null); - } -}; - -BridgeToRedis.prototype.create = function (model, data, callback) { - if (data.id) return create.call(this, data.id, true); - - this.client.incr('id:' + model, function (err, id) { - create.call(this, id); - }.bind(this)); - - function create(id, upsert) { - data.id = id.toString(); - this.save(model, data, function (err) { - if (callback) { - callback(err, parseInt(id, 10)); - } - }); - - // push the id to the list of user ids for sorting - this.client.sadd(['s:' + model, data.id]); - } -}; - -BridgeToRedis.prototype.updateOrCreate = function (model, data, callback) { - if (!data.id) { - return this.create(model, data, callback); - } - var client = this.client; - this.save(model, data, function (error, obj) { - var key = 'id:' + model; - client.get(key, function (err, id) { - if (!id || data.id > parseInt(id, 10)) { - client.set([key, data.id], ok); - } else { - ok(); - } - }); - function ok() { - callback(error, obj); - } - }); -}; - -BridgeToRedis.prototype.exists = function (model, id, callback) { - this.client.exists(model + ':' + id, function (err, exists) { - if (callback) { - callback(err, exists); - } - }); -}; - -BridgeToRedis.prototype.find = function find(model, id, callback) { - this.client.hgetall(model + ':' + id, function (err, data) { - if (data && Object.keys(data).length > 0) { - data.id = id; - } else { - data = null; - } - callback(err, this.fromDb(model, data)); - }.bind(this)); -}; - -BridgeToRedis.prototype.destroy = function destroy(model, id, callback) { - var br = this; - var tr = br.client.transaction(); - - br.client.hgetall(model + ':' + id, function (err, data) { - if (err) return callback(err); - - tr.srem(['s:' + model, id]); - tr.del(model + ':' + id); - tr.run(function (err) { - if (err) return callback(err); - callback.removed = true; - - br.updateIndexes(model, id, {}, callback, data); - }); - }); -}; - -BridgeToRedis.prototype.possibleIndexes = function (model, filter) { - if (!filter || Object.keys(filter.where || {}).length === 0) return false; - - var foundIndex = []; - var noIndex = []; - Object.keys(filter.where).forEach(function (key) { - var i = this.indexes[model][key]; - if (i) { - var val = filter.where[key]; - if (i.name === 'Date') { - val = val && val.getTime ? val.getTime() : 0; - } - foundIndex.push('i:' + model + ':' + key + ':' + val); - } else { - noIndex.push(key); - } - }.bind(this)); - - return [foundIndex, noIndex]; -}; - -BridgeToRedis.prototype.all = function all(model, filter, callback) { - var ts = Date.now(); - var client = this.client; - var cmd; - var redis = this; - var sortCmd = []; - var props = this._models[model].properties; - var allNumeric = true; - var dest = 'temp' + (Date.now() * Math.random()); - var innerSetUsed = false; - var trans = this.client.transaction(); - - if (!filter) { - filter = {order: 'id'}; - } - - // WHERE - if (filter.where) { - var pi = this.possibleIndexes(model, filter); - var indexes = pi[0]; - var noIndexes = pi[1]; - - if (noIndexes.length) { - throw new Error(model + ': no indexes found for ' + - noIndexes.join(', ') + - ' impossible to sort and filter using redis adapter'); - } - - if (indexes && indexes.length) { - innerSetUsed = true; - if (indexes.length > 1) { - indexes.unshift(dest); - trans.sinterstore(indexes); - } else { - dest = indexes[0]; - } - } else { - throw new Error('No indexes for ' + filter.where); - } - } else { - dest = 's:' + model; - // no filtering, just sort/limit (if any) - } - - // only counting? - if (filter.getCount) { - trans.scard(dest, callback); - return trans.run(); - } - - // ORDER - var reverse = false; - if (!filter.order) { - filter.order = 'id'; - } - var orders = filter.order; - if (typeof filter.order === "string"){ - orders = [filter.order]; - } - - orders.forEach(function (key) { - var m = key.match(/\s+(A|DE)SC$/i); - if (m) { - key = key.replace(/\s+(A|DE)SC/i, ''); - if (m[1] === 'DE') reverse = true; - } - if (key !== 'id') { - if (props[key].type.name !== 'Number' && props[key].type.name !== 'Date') { - allNumeric = false; - } - } - sortCmd.push("BY", model + ":*->" + key); - }); - - // LIMIT - if (filter.limit){ - var offset = (filter.offset || 0), limit = filter.limit; - sortCmd.push("LIMIT", offset, limit); - } - - // we need ALPHA modifier when sorting string values - // the only case it's not required - we sort numbers - if (!allNumeric) { - sortCmd.push('ALPHA'); - } - - if (reverse) { - sortCmd.push('DESC'); - } - - sortCmd.unshift(dest); - sortCmd.push("GET", "#"); - cmd = "SORT " + sortCmd.join(" "); - var ttt = Date.now(); - trans.sort(sortCmd, function (err, ids){ - if (err) { - return callback(err, []); - } - var sortedKeys = ids.map(function (i) { - return model + ":" + i; - }); - handleKeys(err, sortedKeys); - }); - - if (dest.match(/^temp/)) { - trans.del(dest); - } - - trans.run(callback); - - function handleKeys(err, keys) { - var t2 = Date.now(); - var query = keys.map(function (key) { - return ['hgetall', key]; - }); - client.multi(query, function (err, replies) { - callback(err, (replies || []).map(function (r) { - return redis.fromDb(model, r); - })); - }); - } - - return; - - function numerically(a, b) { - return a[this[0]] - b[this[0]]; - } - - function literally(a, b) { - return a[this[0]] > b[this[0]]; - } - -}; - -BridgeToRedis.prototype.destroyAll = function destroyAll(model, callback) { - var br = this; - this.client.multi([ - ['KEYS', model + ':*'], - ['KEYS', '*:' + model + ':*'] - ], function (err, k) { - br.client.del(k[0].concat(k[1]).concat('s:' + model), callback); - }); - -}; - -BridgeToRedis.prototype.count = function count(model, callback, where) { - if (where && Object.keys(where).length) { - this.all(model, {where: where, getCount: true}, callback); - } else { - this.client.scard('s:' + model, callback); - } -}; - -BridgeToRedis.prototype.updateAttributes = function updateAttrs(model, id, data, cb) { - data.id = id; - this.save(model, data, cb); -}; - -function deleteNulls(data) { - Object.keys(data).forEach(function (key) { - if (data[key] === null) delete data[key]; - }); -} - -BridgeToRedis.prototype.disconnect = function disconnect() { - this.client.quit(); -}; - diff --git a/lib/schema.js b/lib/schema.js index c146421a..8c2e1905 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -62,9 +62,14 @@ function Schema(name, settings) { // this is only one initialization entry point of adapter // this module should define `adapter` member of `this` (schema) var adapter; - if (existsSync(__dirname + '/adapters/' + name + '.js')) { + if (name.match(/^\//)) { + // try absolute path + adapter = require(name); + } else if (existsSync(__dirname + '/adapters/' + name + '.js')) { + // try built-in adapter adapter = require('./adapters/' + name); } else { + // try foreign adapter try { adapter = require('jugglingdb-' + name); } catch (e) { diff --git a/package.json b/package.json index b675fba5..ac0de152 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "jugglingdb", "description": "ORM for every database: redis, mysql, neo4j, mongodb, couchdb, postgres, sqlite", - "version": "0.1.27-3", + "version": "0.2.0", "author": "Anatoliy Chakkaev ", "contributors": [ { @@ -51,7 +51,7 @@ }, "main": "index.js", "scripts": { - "test": "EXCEPT=cradle,neo4j nodeunit test/*_test*" + "test": "EXCEPT=cradle,neo4j,nano nodeunit test/*_test*" }, "engines": [ "node >= 0.4.12" diff --git a/test/common_test.js b/test/common_test.js index c26e7a6d..d080793b 100644 --- a/test/common_test.js +++ b/test/common_test.js @@ -1,63 +1,53 @@ -require('./spec_helper').init(exports); var Schema = require('../index').Schema; var Text = Schema.Text; -var schemas = { - // riak: {}, - mysql: { - database: 'myapp_test', - username: 'root' - }, - postgres: { - database: 'myapp_test', - username: 'postgres' - }, - sqlite3: { - database: ':memory:' - }, - neo4j: { url: 'http://localhost:7474/' }, - // mongoose: { url: 'mongodb://travis:test@localhost:27017/myapp' }, - mongodb: { url: 'mongodb://travis:test@localhost:27017/myapp' }, - redis2: {}, - memory: {}, - cradle: {}, - nano: { url: 'http://localhost:5984/nano-test' } -}; +// var schemas = { +// // riak: {}, +// mysql: { +// database: 'myapp_test', +// username: 'root' +// }, +// postgres: { +// database: 'myapp_test', +// username: 'postgres' +// }, +// sqlite3: { +// database: ':memory:' +// }, +// neo4j: { url: 'http://localhost:7474/' }, +// // mongoose: { url: 'mongodb://travis:test@localhost:27017/myapp' }, +// mongodb: { url: 'mongodb://travis:test@localhost:27017/myapp' }, +// redis2: {}, +// memory: {}, +// cradle: {}, +// nano: { url: 'http://localhost:5984/nano-test' } +// }; -var specificTest = getSpecificTests(); -var testPerformed = false; var nbSchemaRequests = 0; -Object.keys(schemas).forEach(function (schemaName) { - if (process.env.ONLY && process.env.ONLY !== schemaName) return; - if (process.env.EXCEPT && ~process.env.EXCEPT.indexOf(schemaName)) return; - performTestFor(schemaName); -}); +var batch; -if (process.env.ONLY && !testPerformed) { - performTestFor(process.env.ONLY); +function it(name, cases) { + batch[name] = cases; } -function performTestFor(schemaName) { - testPerformed = true; - context(schemaName, function () { - var schema = new Schema(schemaName, schemas[schemaName] || {}); +module.exports = function testSchema(exportCasesHere, schema) { - it('should connect to database', function (test) { - if (schema.connected) return test.done(); - schema.on('connected', test.done); - }); + batch = exportCasesHere; - schema.log = function (a) { - console.log(a); - nbSchemaRequests++; - }; - - testOrm(schema); - if (specificTest[schemaName]) specificTest[schemaName](schema); + it('should connect to database', function (test) { + if (schema.connected) return test.done(); + schema.on('connected', test.done); }); -} + + schema.log = function (a) { + console.log(a); + nbSchemaRequests++; + }; + + testOrm(schema); +}; function testOrm(schema) { var requestsAreCounted = schema.name !== 'mongodb'; @@ -454,108 +444,108 @@ function testOrm(schema) { }); - if ( - !schema.name.match(/redis/) && - schema.name !== 'memory' && - schema.name !== 'neo4j' && - schema.name !== 'cradle' && - schema.name !== 'nano' && - schema.name !== 'mongodb' - ) - it('hasMany should support additional conditions', function (test) { + // if ( + // !schema.name.match(/redis/) && + // schema.name !== 'memory' && + // schema.name !== 'neo4j' && + // schema.name !== 'cradle' && + // schema.name !== 'nano' && + // schema.name !== 'mongodb' + // ) + // it('hasMany should support additional conditions', function (test) { - // Finding one post with an existing author associated - Post.all(function (err, posts) { - // We try to get the first post with a userId != NULL - for (var i = 0; i < posts.length; i++) { - var post = posts[i]; - if (post.userId !== null) { - // We could get the user with belongs to relationship but it is better if there is no interactions. - User.find(post.userId, function(err, user) { - user.posts({where: {id: post.id}}, function(err, posts) { - test.equal(posts.length, 1, 'There should be only 1 post.'); - test.done(); - }); - }); - break; - } - } - }); - }); + // // Finding one post with an existing author associated + // Post.all(function (err, posts) { + // // We try to get the first post with a userId != NULL + // for (var i = 0; i < posts.length; i++) { + // var post = posts[i]; + // if (post.userId !== null) { + // // We could get the user with belongs to relationship but it is better if there is no interactions. + // User.find(post.userId, function(err, user) { + // user.posts({where: {id: post.id}}, function(err, posts) { + // test.equal(posts.length, 1, 'There should be only 1 post.'); + // test.done(); + // }); + // }); + // break; + // } + // } + // }); + // }); - if ( - !schema.name.match(/redis/) && - schema.name !== 'memory' && - schema.name !== 'neo4j' && - schema.name !== 'cradle' && - schema.name !== 'nano' - ) - it('hasMany should be cached', function (test) { - // Finding one post with an existing author associated - Post.all(function (err, posts) { - // We try to get the first post with a userId != NULL - for (var i = 0; i < posts.length; i++) { - var post = posts[i]; - if (post.userId !== null) { - // We could get the user with belongs to relationship but it is better if there is no interactions. - User.find(post.userId, function(err, user) { - User.create(function(err, voidUser) { - Post.create({userId: user.id}, function() { + // if ( + // !schema.name.match(/redis/) && + // schema.name !== 'memory' && + // schema.name !== 'neo4j' && + // schema.name !== 'cradle' && + // schema.name !== 'nano' + // ) + // it('hasMany should be cached', function (test) { + // // Finding one post with an existing author associated + // Post.all(function (err, posts) { + // // We try to get the first post with a userId != NULL + // for (var i = 0; i < posts.length; i++) { + // var post = posts[i]; + // if (post.userId !== null) { + // // We could get the user with belongs to relationship but it is better if there is no interactions. + // User.find(post.userId, function(err, user) { + // User.create(function(err, voidUser) { + // Post.create({userId: user.id}, function() { - // There can't be any concurrency because we are counting requests - // We are first testing cases when user has posts - user.posts(function(err, data) { - var nbInitialRequests = nbSchemaRequests; - user.posts(function(err, data2) { - test.equal(data.length, 2, 'There should be 2 posts.'); - test.equal(data.length, data2.length, 'Posts should be the same, since we are loading on the same object.'); - requestsAreCounted && test.equal(nbInitialRequests, nbSchemaRequests, 'There should not be any request because value is cached.'); + // // There can't be any concurrency because we are counting requests + // // We are first testing cases when user has posts + // user.posts(function(err, data) { + // var nbInitialRequests = nbSchemaRequests; + // user.posts(function(err, data2) { + // test.equal(data.length, 2, 'There should be 2 posts.'); + // test.equal(data.length, data2.length, 'Posts should be the same, since we are loading on the same object.'); + // requestsAreCounted && test.equal(nbInitialRequests, nbSchemaRequests, 'There should not be any request because value is cached.'); - if (schema.name === 'mongodb') { // for the moment mongodb doesn\'t support additional conditions on hasMany relations (see above) - test.done(); - } else { - user.posts({where: {id: data[0].id}}, function(err, data) { - test.equal(data.length, 1, 'There should be only one post.'); - requestsAreCounted && test.equal(nbInitialRequests + 1, nbSchemaRequests, 'There should be one additional request since we added conditions.'); + // if (schema.name === 'mongodb') { // for the moment mongodb doesn\'t support additional conditions on hasMany relations (see above) + // test.done(); + // } else { + // user.posts({where: {id: data[0].id}}, function(err, data) { + // test.equal(data.length, 1, 'There should be only one post.'); + // requestsAreCounted && test.equal(nbInitialRequests + 1, nbSchemaRequests, 'There should be one additional request since we added conditions.'); - user.posts(function(err, data) { - test.equal(data.length, 2, 'Previous get shouldn\'t have changed cached value though, since there was additional conditions.'); - requestsAreCounted && test.equal(nbInitialRequests + 1, nbSchemaRequests, 'There should not be any request because value is cached.'); + // user.posts(function(err, data) { + // test.equal(data.length, 2, 'Previous get shouldn\'t have changed cached value though, since there was additional conditions.'); + // requestsAreCounted && test.equal(nbInitialRequests + 1, nbSchemaRequests, 'There should not be any request because value is cached.'); - // We are now testing cases when user doesn't have any post - voidUser.posts(function(err, data) { - var nbInitialRequests = nbSchemaRequests; - voidUser.posts(function(err, data2) { - test.equal(data.length, 0, 'There shouldn\'t be any posts (1/2).'); - test.equal(data2.length, 0, 'There shouldn\'t be any posts (2/2).'); - requestsAreCounted && test.equal(nbInitialRequests, nbSchemaRequests, 'There should not be any request because value is cached.'); + // // We are now testing cases when user doesn't have any post + // voidUser.posts(function(err, data) { + // var nbInitialRequests = nbSchemaRequests; + // voidUser.posts(function(err, data2) { + // test.equal(data.length, 0, 'There shouldn\'t be any posts (1/2).'); + // test.equal(data2.length, 0, 'There shouldn\'t be any posts (2/2).'); + // requestsAreCounted && test.equal(nbInitialRequests, nbSchemaRequests, 'There should not be any request because value is cached.'); - voidUser.posts(true, function(err, data3) { - test.equal(data3.length, 0, 'There shouldn\'t be any posts.'); - requestsAreCounted && test.equal(nbInitialRequests + 1, nbSchemaRequests, 'There should be one additional request since we forced refresh.'); + // voidUser.posts(true, function(err, data3) { + // test.equal(data3.length, 0, 'There shouldn\'t be any posts.'); + // requestsAreCounted && test.equal(nbInitialRequests + 1, nbSchemaRequests, 'There should be one additional request since we forced refresh.'); - test.done(); - }); - }); - }); + // test.done(); + // }); + // }); + // }); - }); - }); - } + // }); + // }); + // } - }); - }); + // }); + // }); - }); - }); - }); - break; - } - } - }); + // }); + // }); + // }); + // break; + // } + // } + // }); - }); + // }); // it('should handle hasOne relationship', function (test) { // User.create(function (err, u) { @@ -760,144 +750,144 @@ function testOrm(schema) { }); - if ( - !schema.name.match(/redis/) && - schema.name !== 'memory' && - schema.name !== 'neo4j' && - schema.name !== 'cradle' && - schema.name !== 'nano' - ) - it('should allow advanced queying: lt, gt, lte, gte, between', function (test) { - Post.destroyAll(function () { - Post.create({date: new Date('Wed, 01 Feb 2012 13:56:12 GMT')}, done); - Post.create({date: new Date('Thu, 02 Feb 2012 13:56:12 GMT')}, done); - Post.create({date: new Date('Fri, 03 Feb 2012 13:56:12 GMT')}, done); - Post.create({date: new Date('Sat, 04 Feb 2012 13:56:12 GMT')}, done); - Post.create({date: new Date('Sun, 05 Feb 2012 13:56:12 GMT')}, done); - Post.create({date: new Date('Mon, 06 Feb 2012 13:56:12 GMT')}, done); - Post.create({date: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}, done); - Post.create({date: new Date('Wed, 08 Feb 2012 13:56:12 GMT')}, done); - Post.create({date: new Date('Thu, 09 Feb 2012 13:56:12 GMT')}, done); - }); + // if ( + // !schema.name.match(/redis/) && + // schema.name !== 'memory' && + // schema.name !== 'neo4j' && + // schema.name !== 'cradle' && + // schema.name !== 'nano' + // ) + // it('should allow advanced queying: lt, gt, lte, gte, between', function (test) { + // Post.destroyAll(function () { + // Post.create({date: new Date('Wed, 01 Feb 2012 13:56:12 GMT')}, done); + // Post.create({date: new Date('Thu, 02 Feb 2012 13:56:12 GMT')}, done); + // Post.create({date: new Date('Fri, 03 Feb 2012 13:56:12 GMT')}, done); + // Post.create({date: new Date('Sat, 04 Feb 2012 13:56:12 GMT')}, done); + // Post.create({date: new Date('Sun, 05 Feb 2012 13:56:12 GMT')}, done); + // Post.create({date: new Date('Mon, 06 Feb 2012 13:56:12 GMT')}, done); + // Post.create({date: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}, done); + // Post.create({date: new Date('Wed, 08 Feb 2012 13:56:12 GMT')}, done); + // Post.create({date: new Date('Thu, 09 Feb 2012 13:56:12 GMT')}, done); + // }); - var posts = 9; - function done() { - if (--posts === 0) makeTest(); - } + // var posts = 9; + // function done() { + // if (--posts === 0) makeTest(); + // } - function makeTest() { - // gt - Post.all({where: {date: {gt: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}}}, function (err, posts) { - test.equal(posts.length, 2, 'gt'); - ok(); - }); + // function makeTest() { + // // gt + // Post.all({where: {date: {gt: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}}}, function (err, posts) { + // test.equal(posts.length, 2, 'gt'); + // ok(); + // }); - // gte - Post.all({where: {date: {gte: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}}}, function (err, posts) { - test.equal(posts.length, 3, 'gte'); - ok(); - }); + // // gte + // Post.all({where: {date: {gte: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}}}, function (err, posts) { + // test.equal(posts.length, 3, 'gte'); + // ok(); + // }); - // lte - Post.all({where: {date: {lte: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}}}, function (err, posts) { - test.equal(posts.length, 7, 'lte'); - ok(); - }); + // // lte + // Post.all({where: {date: {lte: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}}}, function (err, posts) { + // test.equal(posts.length, 7, 'lte'); + // ok(); + // }); - // lt - Post.all({where: {date: {lt: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}}}, function (err, posts) { - test.equal(posts.length, 6, 'lt'); - ok(); - }); + // // lt + // Post.all({where: {date: {lt: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}}}, function (err, posts) { + // test.equal(posts.length, 6, 'lt'); + // ok(); + // }); - // between - Post.all({where: {date: {between: [new Date('Tue, 05 Feb 2012 13:56:12 GMT'), new Date('Tue, 09 Feb 2012 13:56:12 GMT')]}}}, function (err, posts) { - test.equal(posts.length, 5, 'between'); - ok(); - }); - } + // // between + // Post.all({where: {date: {between: [new Date('Tue, 05 Feb 2012 13:56:12 GMT'), new Date('Tue, 09 Feb 2012 13:56:12 GMT')]}}}, function (err, posts) { + // test.equal(posts.length, 5, 'between'); + // ok(); + // }); + // } - var tests = 5; - function ok() { - if (--tests === 0) test.done(); - } - }); + // var tests = 5; + // function ok() { + // if (--tests === 0) test.done(); + // } + // }); - if ( - schema.name === 'mysql' || - schema.name === 'postgres' - ) - it('should allow IN or NOT IN', function (test) { - User.destroyAll(function () { - User.create({name: 'User A', age: 21}, done); - User.create({name: 'User B', age: 22}, done); - User.create({name: 'User C', age: 23}, done); - User.create({name: 'User D', age: 24}, done); - User.create({name: 'User E', age: 25}, done); - }); + // if ( + // schema.name === 'mysql' || + // schema.name === 'postgres' + // ) + // it('should allow IN or NOT IN', function (test) { + // User.destroyAll(function () { + // User.create({name: 'User A', age: 21}, done); + // User.create({name: 'User B', age: 22}, done); + // User.create({name: 'User C', age: 23}, done); + // User.create({name: 'User D', age: 24}, done); + // User.create({name: 'User E', age: 25}, done); + // }); - var users = 5; - function done() { - if (--users === 0) makeTest(); - } + // var users = 5; + // function done() { + // if (--users === 0) makeTest(); + // } - function makeTest() { - // IN with empty array should return nothing - User.all({where: {name: {inq: []}}}, function (err, users) { - test.equal(users.length, 0, 'IN with empty array returns nothing'); - ok(); - }); + // function makeTest() { + // // IN with empty array should return nothing + // User.all({where: {name: {inq: []}}}, function (err, users) { + // test.equal(users.length, 0, 'IN with empty array returns nothing'); + // ok(); + // }); - // NOT IN with empty array should return everything - User.all({where: {name: {nin: []}}}, function (err, users) { - test.equal(users.length, 5, 'NOT IN with empty array returns everything'); - ok(); - }); + // // NOT IN with empty array should return everything + // User.all({where: {name: {nin: []}}}, function (err, users) { + // test.equal(users.length, 5, 'NOT IN with empty array returns everything'); + // ok(); + // }); - // IN [User A] returns user with name = User A - User.all({where: {name: {inq: ['User A']}}}, function (err, users) { - test.equal(users.length, 1, 'IN searching one existing value returns 1 user'); - test.equal(users[0].name, 'User A', 'IN [User A] returns user with name = User A'); - ok(); - }); + // // IN [User A] returns user with name = User A + // User.all({where: {name: {inq: ['User A']}}}, function (err, users) { + // test.equal(users.length, 1, 'IN searching one existing value returns 1 user'); + // test.equal(users[0].name, 'User A', 'IN [User A] returns user with name = User A'); + // ok(); + // }); - // NOT IN [User A] returns users with name != User A - User.all({where: {name: {nin: ['User A']}}}, function (err, users) { - test.equal(users.length, 4, 'IN [User A] returns users with name != User A'); - ok(); - }); + // // NOT IN [User A] returns users with name != User A + // User.all({where: {name: {nin: ['User A']}}}, function (err, users) { + // test.equal(users.length, 4, 'IN [User A] returns users with name != User A'); + // ok(); + // }); - // IN [User A, User B] returns users with name = User A OR name = User B - User.all({where: {name: {inq: ['User A', 'User B']}}}, function (err, users) { - test.equal(users.length, 2, 'IN searching two existing values returns 2 users'); - ok(); - }); + // // IN [User A, User B] returns users with name = User A OR name = User B + // User.all({where: {name: {inq: ['User A', 'User B']}}}, function (err, users) { + // test.equal(users.length, 2, 'IN searching two existing values returns 2 users'); + // ok(); + // }); - // NOT IN [User A, User B] returns users with name != User A AND name != User B - User.all({where: {name: {nin: ['User A', 'User B']}}}, function (err, users) { - test.equal(users.length, 3, 'NOT IN searching two existing values returns users with name != User A AND name != User B'); - ok(); - }); + // // NOT IN [User A, User B] returns users with name != User A AND name != User B + // User.all({where: {name: {nin: ['User A', 'User B']}}}, function (err, users) { + // test.equal(users.length, 3, 'NOT IN searching two existing values returns users with name != User A AND name != User B'); + // ok(); + // }); - // IN works with numbers too - User.all({where: {age: {inq: [21, 22]}}}, function (err, users) { - test.equal(users.length, 2, 'IN works with numbers too'); - ok(); - }); + // // IN works with numbers too + // User.all({where: {age: {inq: [21, 22]}}}, function (err, users) { + // test.equal(users.length, 2, 'IN works with numbers too'); + // ok(); + // }); - // NOT IN works with numbers too - User.all({where: {age: {nin: [21, 22]}}}, function (err, users) { - test.equal(users.length, 3, 'NOT IN works with numbers too'); - ok(); - }); - } + // // NOT IN works with numbers too + // User.all({where: {age: {nin: [21, 22]}}}, function (err, users) { + // test.equal(users.length, 3, 'NOT IN works with numbers too'); + // ok(); + // }); + // } - var tests = 8; - function ok() { - if (--tests === 0) test.done(); - } - }); + // var tests = 8; + // function ok() { + // if (--tests === 0) test.done(); + // } + // }); it('should handle order clause with direction', function (test) { var wait = 0; @@ -994,54 +984,54 @@ function testOrm(schema) { }); }); - if ( - !schema.name.match(/redis/) && - schema.name !== 'memory' && - schema.name !== 'neo4j' && - schema.name !== 'cradle' && - schema.name !== 'nano' - ) - it('belongsTo should be cached', function (test) { - User.findOne(function(err, user) { + // if ( + // !schema.name.match(/redis/) && + // schema.name !== 'memory' && + // schema.name !== 'neo4j' && + // schema.name !== 'cradle' && + // schema.name !== 'nano' + // ) + // it('belongsTo should be cached', function (test) { + // User.findOne(function(err, user) { - var passport = new Passport({ownerId: user.id}); - var passport2 = new Passport({ownerId: null}); + // var passport = new Passport({ownerId: user.id}); + // var passport2 = new Passport({ownerId: null}); - // There can't be any concurrency because we are counting requests - // We are first testing cases when passport has an owner - passport.owner(function(err, data) { - var nbInitialRequests = nbSchemaRequests; - passport.owner(function(err, data2) { - test.equal(data.id, data2.id, 'The value should remain the same'); - requestsAreCounted && test.equal(nbInitialRequests, nbSchemaRequests, 'There should not be any request because value is cached.'); + // // There can't be any concurrency because we are counting requests + // // We are first testing cases when passport has an owner + // passport.owner(function(err, data) { + // var nbInitialRequests = nbSchemaRequests; + // passport.owner(function(err, data2) { + // test.equal(data.id, data2.id, 'The value should remain the same'); + // requestsAreCounted && test.equal(nbInitialRequests, nbSchemaRequests, 'There should not be any request because value is cached.'); - // We are now testing cases when passport has not an owner - passport2.owner(function(err, data) { - var nbInitialRequests2 = nbSchemaRequests; - passport2.owner(function(err, data2) { - test.equal(data, null, 'The value should be null since there is no owner'); - test.equal(data, data2, 'The value should remain the same (null)'); - requestsAreCounted && test.equal(nbInitialRequests2, nbSchemaRequests, 'There should not be any request because value is cached.'); + // // We are now testing cases when passport has not an owner + // passport2.owner(function(err, data) { + // var nbInitialRequests2 = nbSchemaRequests; + // passport2.owner(function(err, data2) { + // test.equal(data, null, 'The value should be null since there is no owner'); + // test.equal(data, data2, 'The value should remain the same (null)'); + // requestsAreCounted && test.equal(nbInitialRequests2, nbSchemaRequests, 'There should not be any request because value is cached.'); - passport2.owner(user.id); - passport2.owner(function(err, data3) { - test.equal(data3.id, user.id, 'Owner should now be the user.'); - requestsAreCounted && test.equal(nbInitialRequests2 + 1, nbSchemaRequests, 'If we changed owner id, there should be one more request.'); + // passport2.owner(user.id); + // passport2.owner(function(err, data3) { + // test.equal(data3.id, user.id, 'Owner should now be the user.'); + // requestsAreCounted && test.equal(nbInitialRequests2 + 1, nbSchemaRequests, 'If we changed owner id, there should be one more request.'); - passport2.owner(true, function(err, data4) { - test.equal(data3.id, data3.id, 'The value should remain the same'); - requestsAreCounted && test.equal(nbInitialRequests2 + 2, nbSchemaRequests, 'If we forced refreshing, there should be one more request.'); - test.done(); - }); - }); - }); - }); + // passport2.owner(true, function(err, data4) { + // test.equal(data3.id, data3.id, 'The value should remain the same'); + // requestsAreCounted && test.equal(nbInitialRequests2 + 2, nbSchemaRequests, 'If we forced refreshing, there should be one more request.'); + // test.done(); + // }); + // }); + // }); + // }); - }); - }); - }); + // }); + // }); + // }); - }); + // }); if (schema.name !== 'mongoose' && schema.name !== 'neo4j') it('should update or create record', function (test) { @@ -1157,28 +1147,3 @@ function testOrm(schema) { } } - -function getSpecificTests() { - var sp = {}; - - sp['neo4j'] = function (schema) { - - it('should create methods for searching by index', function (test) { - var Post = schema.models['Post']; - test.ok(typeof Post.findByTitle === 'function'); - Post.create({title: 'Catcher in the rye'}, function (err, post) { - if (err) return console.log(err); - test.ok(!post.isNewRecord()); - Post.findByTitle('Catcher in the rye', function (err, foundPost) { - if (err) return console.log(err); - if (foundPost) { - test.equal(post.id, foundPost.id); - test.done(); - } - }); - }); - }); - }; - - return sp; -}