Removed nosql adapters (moved to own repos)
This commit is contained in:
parent
963d393089
commit
a62aab180d
2
index.js
2
index.js
|
@ -22,3 +22,5 @@ try {
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
|
exports.test = require('./test/common_test');
|
||||||
|
|
||||||
|
|
|
@ -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();
|
|
||||||
};
|
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
|
@ -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);
|
|
|
@ -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();
|
|
||||||
};
|
|
||||||
|
|
|
@ -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();
|
|
||||||
};
|
|
||||||
|
|
|
@ -62,9 +62,14 @@ function Schema(name, settings) {
|
||||||
// this is only one initialization entry point of adapter
|
// this is only one initialization entry point of adapter
|
||||||
// this module should define `adapter` member of `this` (schema)
|
// this module should define `adapter` member of `this` (schema)
|
||||||
var adapter;
|
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);
|
adapter = require('./adapters/' + name);
|
||||||
} else {
|
} else {
|
||||||
|
// try foreign adapter
|
||||||
try {
|
try {
|
||||||
adapter = require('jugglingdb-' + name);
|
adapter = require('jugglingdb-' + name);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "jugglingdb",
|
"name": "jugglingdb",
|
||||||
"description": "ORM for every database: redis, mysql, neo4j, mongodb, couchdb, postgres, sqlite",
|
"description": "ORM for every database: redis, mysql, neo4j, mongodb, couchdb, postgres, sqlite",
|
||||||
"version": "0.1.27-3",
|
"version": "0.2.0",
|
||||||
"author": "Anatoliy Chakkaev <rpm1602@gmail.com>",
|
"author": "Anatoliy Chakkaev <rpm1602@gmail.com>",
|
||||||
"contributors": [
|
"contributors": [
|
||||||
{
|
{
|
||||||
|
@ -51,7 +51,7 @@
|
||||||
},
|
},
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "EXCEPT=cradle,neo4j nodeunit test/*_test*"
|
"test": "EXCEPT=cradle,neo4j,nano nodeunit test/*_test*"
|
||||||
},
|
},
|
||||||
"engines": [
|
"engines": [
|
||||||
"node >= 0.4.12"
|
"node >= 0.4.12"
|
||||||
|
|
|
@ -1,63 +1,53 @@
|
||||||
require('./spec_helper').init(exports);
|
|
||||||
|
|
||||||
var Schema = require('../index').Schema;
|
var Schema = require('../index').Schema;
|
||||||
var Text = Schema.Text;
|
var Text = Schema.Text;
|
||||||
|
|
||||||
var schemas = {
|
// var schemas = {
|
||||||
// riak: {},
|
// // riak: {},
|
||||||
mysql: {
|
// mysql: {
|
||||||
database: 'myapp_test',
|
// database: 'myapp_test',
|
||||||
username: 'root'
|
// username: 'root'
|
||||||
},
|
// },
|
||||||
postgres: {
|
// postgres: {
|
||||||
database: 'myapp_test',
|
// database: 'myapp_test',
|
||||||
username: 'postgres'
|
// username: 'postgres'
|
||||||
},
|
// },
|
||||||
sqlite3: {
|
// sqlite3: {
|
||||||
database: ':memory:'
|
// database: ':memory:'
|
||||||
},
|
// },
|
||||||
neo4j: { url: 'http://localhost:7474/' },
|
// neo4j: { url: 'http://localhost:7474/' },
|
||||||
// mongoose: { url: 'mongodb://travis:test@localhost:27017/myapp' },
|
// // mongoose: { url: 'mongodb://travis:test@localhost:27017/myapp' },
|
||||||
mongodb: { url: 'mongodb://travis:test@localhost:27017/myapp' },
|
// mongodb: { url: 'mongodb://travis:test@localhost:27017/myapp' },
|
||||||
redis2: {},
|
// redis2: {},
|
||||||
memory: {},
|
// memory: {},
|
||||||
cradle: {},
|
// cradle: {},
|
||||||
nano: { url: 'http://localhost:5984/nano-test' }
|
// nano: { url: 'http://localhost:5984/nano-test' }
|
||||||
};
|
// };
|
||||||
|
|
||||||
var specificTest = getSpecificTests();
|
|
||||||
var testPerformed = false;
|
|
||||||
var nbSchemaRequests = 0;
|
var nbSchemaRequests = 0;
|
||||||
|
|
||||||
Object.keys(schemas).forEach(function (schemaName) {
|
var batch;
|
||||||
if (process.env.ONLY && process.env.ONLY !== schemaName) return;
|
|
||||||
if (process.env.EXCEPT && ~process.env.EXCEPT.indexOf(schemaName)) return;
|
|
||||||
performTestFor(schemaName);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (process.env.ONLY && !testPerformed) {
|
function it(name, cases) {
|
||||||
performTestFor(process.env.ONLY);
|
batch[name] = cases;
|
||||||
}
|
}
|
||||||
|
|
||||||
function performTestFor(schemaName) {
|
module.exports = function testSchema(exportCasesHere, schema) {
|
||||||
testPerformed = true;
|
|
||||||
context(schemaName, function () {
|
|
||||||
var schema = new Schema(schemaName, schemas[schemaName] || {});
|
|
||||||
|
|
||||||
it('should connect to database', function (test) {
|
batch = exportCasesHere;
|
||||||
if (schema.connected) return test.done();
|
|
||||||
schema.on('connected', test.done);
|
|
||||||
});
|
|
||||||
|
|
||||||
schema.log = function (a) {
|
it('should connect to database', function (test) {
|
||||||
console.log(a);
|
if (schema.connected) return test.done();
|
||||||
nbSchemaRequests++;
|
schema.on('connected', test.done);
|
||||||
};
|
|
||||||
|
|
||||||
testOrm(schema);
|
|
||||||
if (specificTest[schemaName]) specificTest[schemaName](schema);
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
schema.log = function (a) {
|
||||||
|
console.log(a);
|
||||||
|
nbSchemaRequests++;
|
||||||
|
};
|
||||||
|
|
||||||
|
testOrm(schema);
|
||||||
|
};
|
||||||
|
|
||||||
function testOrm(schema) {
|
function testOrm(schema) {
|
||||||
var requestsAreCounted = schema.name !== 'mongodb';
|
var requestsAreCounted = schema.name !== 'mongodb';
|
||||||
|
@ -454,108 +444,108 @@ function testOrm(schema) {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
if (
|
// if (
|
||||||
!schema.name.match(/redis/) &&
|
// !schema.name.match(/redis/) &&
|
||||||
schema.name !== 'memory' &&
|
// schema.name !== 'memory' &&
|
||||||
schema.name !== 'neo4j' &&
|
// schema.name !== 'neo4j' &&
|
||||||
schema.name !== 'cradle' &&
|
// schema.name !== 'cradle' &&
|
||||||
schema.name !== 'nano' &&
|
// schema.name !== 'nano' &&
|
||||||
schema.name !== 'mongodb'
|
// schema.name !== 'mongodb'
|
||||||
)
|
// )
|
||||||
it('hasMany should support additional conditions', function (test) {
|
// it('hasMany should support additional conditions', function (test) {
|
||||||
|
|
||||||
// Finding one post with an existing author associated
|
// // Finding one post with an existing author associated
|
||||||
Post.all(function (err, posts) {
|
// Post.all(function (err, posts) {
|
||||||
// We try to get the first post with a userId != NULL
|
// // We try to get the first post with a userId != NULL
|
||||||
for (var i = 0; i < posts.length; i++) {
|
// for (var i = 0; i < posts.length; i++) {
|
||||||
var post = posts[i];
|
// var post = posts[i];
|
||||||
if (post.userId !== null) {
|
// if (post.userId !== null) {
|
||||||
// We could get the user with belongs to relationship but it is better if there is no interactions.
|
// // 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.find(post.userId, function(err, user) {
|
||||||
user.posts({where: {id: post.id}}, function(err, posts) {
|
// user.posts({where: {id: post.id}}, function(err, posts) {
|
||||||
test.equal(posts.length, 1, 'There should be only 1 post.');
|
// test.equal(posts.length, 1, 'There should be only 1 post.');
|
||||||
test.done();
|
// test.done();
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
break;
|
// break;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
|
|
||||||
|
|
||||||
if (
|
// if (
|
||||||
!schema.name.match(/redis/) &&
|
// !schema.name.match(/redis/) &&
|
||||||
schema.name !== 'memory' &&
|
// schema.name !== 'memory' &&
|
||||||
schema.name !== 'neo4j' &&
|
// schema.name !== 'neo4j' &&
|
||||||
schema.name !== 'cradle' &&
|
// schema.name !== 'cradle' &&
|
||||||
schema.name !== 'nano'
|
// schema.name !== 'nano'
|
||||||
)
|
// )
|
||||||
it('hasMany should be cached', function (test) {
|
// it('hasMany should be cached', function (test) {
|
||||||
// Finding one post with an existing author associated
|
// // Finding one post with an existing author associated
|
||||||
Post.all(function (err, posts) {
|
// Post.all(function (err, posts) {
|
||||||
// We try to get the first post with a userId != NULL
|
// // We try to get the first post with a userId != NULL
|
||||||
for (var i = 0; i < posts.length; i++) {
|
// for (var i = 0; i < posts.length; i++) {
|
||||||
var post = posts[i];
|
// var post = posts[i];
|
||||||
if (post.userId !== null) {
|
// if (post.userId !== null) {
|
||||||
// We could get the user with belongs to relationship but it is better if there is no interactions.
|
// // 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.find(post.userId, function(err, user) {
|
||||||
User.create(function(err, voidUser) {
|
// User.create(function(err, voidUser) {
|
||||||
Post.create({userId: user.id}, function() {
|
// Post.create({userId: user.id}, function() {
|
||||||
|
|
||||||
// There can't be any concurrency because we are counting requests
|
// // There can't be any concurrency because we are counting requests
|
||||||
// We are first testing cases when user has posts
|
// // We are first testing cases when user has posts
|
||||||
user.posts(function(err, data) {
|
// user.posts(function(err, data) {
|
||||||
var nbInitialRequests = nbSchemaRequests;
|
// var nbInitialRequests = nbSchemaRequests;
|
||||||
user.posts(function(err, data2) {
|
// user.posts(function(err, data2) {
|
||||||
test.equal(data.length, 2, 'There should be 2 posts.');
|
// 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.');
|
// 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.');
|
// 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)
|
// if (schema.name === 'mongodb') { // for the moment mongodb doesn\'t support additional conditions on hasMany relations (see above)
|
||||||
test.done();
|
// test.done();
|
||||||
} else {
|
// } else {
|
||||||
user.posts({where: {id: data[0].id}}, function(err, data) {
|
// user.posts({where: {id: data[0].id}}, function(err, data) {
|
||||||
test.equal(data.length, 1, 'There should be only one post.');
|
// 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.');
|
// requestsAreCounted && test.equal(nbInitialRequests + 1, nbSchemaRequests, 'There should be one additional request since we added conditions.');
|
||||||
|
|
||||||
user.posts(function(err, data) {
|
// user.posts(function(err, data) {
|
||||||
test.equal(data.length, 2, 'Previous get shouldn\'t have changed cached value though, since there was additional conditions.');
|
// 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.');
|
// 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
|
// // We are now testing cases when user doesn't have any post
|
||||||
voidUser.posts(function(err, data) {
|
// voidUser.posts(function(err, data) {
|
||||||
var nbInitialRequests = nbSchemaRequests;
|
// var nbInitialRequests = nbSchemaRequests;
|
||||||
voidUser.posts(function(err, data2) {
|
// voidUser.posts(function(err, data2) {
|
||||||
test.equal(data.length, 0, 'There shouldn\'t be any posts (1/2).');
|
// 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).');
|
// 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.');
|
// requestsAreCounted && test.equal(nbInitialRequests, nbSchemaRequests, 'There should not be any request because value is cached.');
|
||||||
|
|
||||||
voidUser.posts(true, function(err, data3) {
|
// voidUser.posts(true, function(err, data3) {
|
||||||
test.equal(data3.length, 0, 'There shouldn\'t be any posts.');
|
// 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.');
|
// 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) {
|
// it('should handle hasOne relationship', function (test) {
|
||||||
// User.create(function (err, u) {
|
// User.create(function (err, u) {
|
||||||
|
@ -760,144 +750,144 @@ function testOrm(schema) {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (
|
// if (
|
||||||
!schema.name.match(/redis/) &&
|
// !schema.name.match(/redis/) &&
|
||||||
schema.name !== 'memory' &&
|
// schema.name !== 'memory' &&
|
||||||
schema.name !== 'neo4j' &&
|
// schema.name !== 'neo4j' &&
|
||||||
schema.name !== 'cradle' &&
|
// schema.name !== 'cradle' &&
|
||||||
schema.name !== 'nano'
|
// schema.name !== 'nano'
|
||||||
)
|
// )
|
||||||
it('should allow advanced queying: lt, gt, lte, gte, between', function (test) {
|
// it('should allow advanced queying: lt, gt, lte, gte, between', function (test) {
|
||||||
Post.destroyAll(function () {
|
// Post.destroyAll(function () {
|
||||||
Post.create({date: new Date('Wed, 01 Feb 2012 13:56:12 GMT')}, done);
|
// 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('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('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('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('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('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('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('Wed, 08 Feb 2012 13:56:12 GMT')}, done);
|
||||||
Post.create({date: new Date('Thu, 09 Feb 2012 13:56:12 GMT')}, done);
|
// Post.create({date: new Date('Thu, 09 Feb 2012 13:56:12 GMT')}, done);
|
||||||
});
|
// });
|
||||||
|
|
||||||
var posts = 9;
|
// var posts = 9;
|
||||||
function done() {
|
// function done() {
|
||||||
if (--posts === 0) makeTest();
|
// if (--posts === 0) makeTest();
|
||||||
}
|
// }
|
||||||
|
|
||||||
function makeTest() {
|
// function makeTest() {
|
||||||
// gt
|
// // gt
|
||||||
Post.all({where: {date: {gt: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}}}, function (err, posts) {
|
// Post.all({where: {date: {gt: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}}}, function (err, posts) {
|
||||||
test.equal(posts.length, 2, 'gt');
|
// test.equal(posts.length, 2, 'gt');
|
||||||
ok();
|
// ok();
|
||||||
});
|
// });
|
||||||
|
|
||||||
// gte
|
// // gte
|
||||||
Post.all({where: {date: {gte: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}}}, function (err, posts) {
|
// Post.all({where: {date: {gte: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}}}, function (err, posts) {
|
||||||
test.equal(posts.length, 3, 'gte');
|
// test.equal(posts.length, 3, 'gte');
|
||||||
ok();
|
// ok();
|
||||||
});
|
// });
|
||||||
|
|
||||||
// lte
|
// // lte
|
||||||
Post.all({where: {date: {lte: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}}}, function (err, posts) {
|
// Post.all({where: {date: {lte: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}}}, function (err, posts) {
|
||||||
test.equal(posts.length, 7, 'lte');
|
// test.equal(posts.length, 7, 'lte');
|
||||||
ok();
|
// ok();
|
||||||
});
|
// });
|
||||||
|
|
||||||
// lt
|
// // lt
|
||||||
Post.all({where: {date: {lt: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}}}, function (err, posts) {
|
// Post.all({where: {date: {lt: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}}}, function (err, posts) {
|
||||||
test.equal(posts.length, 6, 'lt');
|
// test.equal(posts.length, 6, 'lt');
|
||||||
ok();
|
// ok();
|
||||||
});
|
// });
|
||||||
|
|
||||||
// between
|
// // 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) {
|
// 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');
|
// test.equal(posts.length, 5, 'between');
|
||||||
ok();
|
// ok();
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
var tests = 5;
|
// var tests = 5;
|
||||||
function ok() {
|
// function ok() {
|
||||||
if (--tests === 0) test.done();
|
// if (--tests === 0) test.done();
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
|
|
||||||
|
|
||||||
if (
|
// if (
|
||||||
schema.name === 'mysql' ||
|
// schema.name === 'mysql' ||
|
||||||
schema.name === 'postgres'
|
// schema.name === 'postgres'
|
||||||
)
|
// )
|
||||||
it('should allow IN or NOT IN', function (test) {
|
// it('should allow IN or NOT IN', function (test) {
|
||||||
User.destroyAll(function () {
|
// User.destroyAll(function () {
|
||||||
User.create({name: 'User A', age: 21}, done);
|
// User.create({name: 'User A', age: 21}, done);
|
||||||
User.create({name: 'User B', age: 22}, done);
|
// User.create({name: 'User B', age: 22}, done);
|
||||||
User.create({name: 'User C', age: 23}, done);
|
// User.create({name: 'User C', age: 23}, done);
|
||||||
User.create({name: 'User D', age: 24}, done);
|
// User.create({name: 'User D', age: 24}, done);
|
||||||
User.create({name: 'User E', age: 25}, done);
|
// User.create({name: 'User E', age: 25}, done);
|
||||||
});
|
// });
|
||||||
|
|
||||||
var users = 5;
|
// var users = 5;
|
||||||
function done() {
|
// function done() {
|
||||||
if (--users === 0) makeTest();
|
// if (--users === 0) makeTest();
|
||||||
}
|
// }
|
||||||
|
|
||||||
function makeTest() {
|
// function makeTest() {
|
||||||
// IN with empty array should return nothing
|
// // IN with empty array should return nothing
|
||||||
User.all({where: {name: {inq: []}}}, function (err, users) {
|
// User.all({where: {name: {inq: []}}}, function (err, users) {
|
||||||
test.equal(users.length, 0, 'IN with empty array returns nothing');
|
// test.equal(users.length, 0, 'IN with empty array returns nothing');
|
||||||
ok();
|
// ok();
|
||||||
});
|
// });
|
||||||
|
|
||||||
// NOT IN with empty array should return everything
|
// // NOT IN with empty array should return everything
|
||||||
User.all({where: {name: {nin: []}}}, function (err, users) {
|
// User.all({where: {name: {nin: []}}}, function (err, users) {
|
||||||
test.equal(users.length, 5, 'NOT IN with empty array returns everything');
|
// test.equal(users.length, 5, 'NOT IN with empty array returns everything');
|
||||||
ok();
|
// ok();
|
||||||
});
|
// });
|
||||||
|
|
||||||
// IN [User A] returns user with name = User A
|
// // IN [User A] returns user with name = User A
|
||||||
User.all({where: {name: {inq: ['User A']}}}, function (err, users) {
|
// 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.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');
|
// test.equal(users[0].name, 'User A', 'IN [User A] returns user with name = User A');
|
||||||
ok();
|
// ok();
|
||||||
});
|
// });
|
||||||
|
|
||||||
// NOT IN [User A] returns users with name != User A
|
// // NOT IN [User A] returns users with name != User A
|
||||||
User.all({where: {name: {nin: ['User A']}}}, function (err, users) {
|
// User.all({where: {name: {nin: ['User A']}}}, function (err, users) {
|
||||||
test.equal(users.length, 4, 'IN [User A] returns users with name != User A');
|
// test.equal(users.length, 4, 'IN [User A] returns users with name != User A');
|
||||||
ok();
|
// ok();
|
||||||
});
|
// });
|
||||||
|
|
||||||
// IN [User A, User B] returns users with name = User A OR name = User B
|
// // 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) {
|
// 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');
|
// test.equal(users.length, 2, 'IN searching two existing values returns 2 users');
|
||||||
ok();
|
// ok();
|
||||||
});
|
// });
|
||||||
|
|
||||||
// NOT IN [User A, User B] returns users with name != User A AND name != User B
|
// // 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) {
|
// 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');
|
// test.equal(users.length, 3, 'NOT IN searching two existing values returns users with name != User A AND name != User B');
|
||||||
ok();
|
// ok();
|
||||||
});
|
// });
|
||||||
|
|
||||||
// IN works with numbers too
|
// // IN works with numbers too
|
||||||
User.all({where: {age: {inq: [21, 22]}}}, function (err, users) {
|
// User.all({where: {age: {inq: [21, 22]}}}, function (err, users) {
|
||||||
test.equal(users.length, 2, 'IN works with numbers too');
|
// test.equal(users.length, 2, 'IN works with numbers too');
|
||||||
ok();
|
// ok();
|
||||||
});
|
// });
|
||||||
|
|
||||||
// NOT IN works with numbers too
|
// // NOT IN works with numbers too
|
||||||
User.all({where: {age: {nin: [21, 22]}}}, function (err, users) {
|
// User.all({where: {age: {nin: [21, 22]}}}, function (err, users) {
|
||||||
test.equal(users.length, 3, 'NOT IN works with numbers too');
|
// test.equal(users.length, 3, 'NOT IN works with numbers too');
|
||||||
ok();
|
// ok();
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
var tests = 8;
|
// var tests = 8;
|
||||||
function ok() {
|
// function ok() {
|
||||||
if (--tests === 0) test.done();
|
// if (--tests === 0) test.done();
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
|
|
||||||
it('should handle order clause with direction', function (test) {
|
it('should handle order clause with direction', function (test) {
|
||||||
var wait = 0;
|
var wait = 0;
|
||||||
|
@ -994,54 +984,54 @@ function testOrm(schema) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
if (
|
// if (
|
||||||
!schema.name.match(/redis/) &&
|
// !schema.name.match(/redis/) &&
|
||||||
schema.name !== 'memory' &&
|
// schema.name !== 'memory' &&
|
||||||
schema.name !== 'neo4j' &&
|
// schema.name !== 'neo4j' &&
|
||||||
schema.name !== 'cradle' &&
|
// schema.name !== 'cradle' &&
|
||||||
schema.name !== 'nano'
|
// schema.name !== 'nano'
|
||||||
)
|
// )
|
||||||
it('belongsTo should be cached', function (test) {
|
// it('belongsTo should be cached', function (test) {
|
||||||
User.findOne(function(err, user) {
|
// User.findOne(function(err, user) {
|
||||||
|
|
||||||
var passport = new Passport({ownerId: user.id});
|
// var passport = new Passport({ownerId: user.id});
|
||||||
var passport2 = new Passport({ownerId: null});
|
// var passport2 = new Passport({ownerId: null});
|
||||||
|
|
||||||
// There can't be any concurrency because we are counting requests
|
// // There can't be any concurrency because we are counting requests
|
||||||
// We are first testing cases when passport has an owner
|
// // We are first testing cases when passport has an owner
|
||||||
passport.owner(function(err, data) {
|
// passport.owner(function(err, data) {
|
||||||
var nbInitialRequests = nbSchemaRequests;
|
// var nbInitialRequests = nbSchemaRequests;
|
||||||
passport.owner(function(err, data2) {
|
// passport.owner(function(err, data2) {
|
||||||
test.equal(data.id, data2.id, 'The value should remain the same');
|
// 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.');
|
// 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
|
// // We are now testing cases when passport has not an owner
|
||||||
passport2.owner(function(err, data) {
|
// passport2.owner(function(err, data) {
|
||||||
var nbInitialRequests2 = nbSchemaRequests;
|
// var nbInitialRequests2 = nbSchemaRequests;
|
||||||
passport2.owner(function(err, data2) {
|
// passport2.owner(function(err, data2) {
|
||||||
test.equal(data, null, 'The value should be null since there is no owner');
|
// 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)');
|
// 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.');
|
// requestsAreCounted && test.equal(nbInitialRequests2, nbSchemaRequests, 'There should not be any request because value is cached.');
|
||||||
|
|
||||||
passport2.owner(user.id);
|
// passport2.owner(user.id);
|
||||||
passport2.owner(function(err, data3) {
|
// passport2.owner(function(err, data3) {
|
||||||
test.equal(data3.id, user.id, 'Owner should now be the user.');
|
// 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.');
|
// requestsAreCounted && test.equal(nbInitialRequests2 + 1, nbSchemaRequests, 'If we changed owner id, there should be one more request.');
|
||||||
|
|
||||||
passport2.owner(true, function(err, data4) {
|
// passport2.owner(true, function(err, data4) {
|
||||||
test.equal(data3.id, data3.id, 'The value should remain the same');
|
// 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.');
|
// requestsAreCounted && test.equal(nbInitialRequests2 + 2, nbSchemaRequests, 'If we forced refreshing, there should be one more request.');
|
||||||
test.done();
|
// test.done();
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
|
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
|
|
||||||
});
|
// });
|
||||||
|
|
||||||
if (schema.name !== 'mongoose' && schema.name !== 'neo4j')
|
if (schema.name !== 'mongoose' && schema.name !== 'neo4j')
|
||||||
it('should update or create record', function (test) {
|
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;
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue