Removed nosql adapters (moved to own repos)

This commit is contained in:
Anatoliy Chakkaev 2012-12-14 01:50:02 +04:00
parent 963d393089
commit a62aab180d
9 changed files with 296 additions and 2144 deletions

View File

@ -22,3 +22,5 @@ try {
}
} catch (e) {}
exports.test = require('./test/common_test');

View File

@ -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();
};

View File

@ -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;
}

View File

@ -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);

View File

@ -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();
};

View File

@ -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();
};

View File

@ -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) {

View File

@ -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 <rpm1602@gmail.com>",
"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"

View File

@ -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;
}