backbone-db-redis
Advanced tools
Comparing version 0.0.37 to 0.0.38
19
index.js
@@ -7,3 +7,4 @@ var _ = require('lodash'), | ||
indexing = require('./lib/indexing'), | ||
query = require('./lib/query'); | ||
query = require('./lib/query'), | ||
utils = require('./lib/utils'); | ||
@@ -351,2 +352,3 @@ | ||
var getReadFn = function() { | ||
var params; | ||
if (collection.indexSort) { | ||
@@ -358,3 +360,3 @@ var min = '-inf'; | ||
max = options.score.max || max; | ||
var params = [setKey, max, min]; | ||
params = [setKey, max, min]; | ||
if (options.score.conversion) params.push('WITHSCORES'); | ||
@@ -371,2 +373,15 @@ if (options.limit || options.offset) { | ||
} | ||
if (options.sort && collection.model.prototype.redis_type === 'hash') { | ||
var m = new collection.model(); | ||
var idKey = self.getIdKey(m); | ||
var parsedSort = utils.parseSort(options.sort); | ||
var sortParams = idKey + ':*->' + parsedSort.sortProp; | ||
params = [setKey, 'BY', sortParams]; | ||
if (parsedSort.sortOrder === -1) { | ||
params.push('DESC'); | ||
} | ||
debug('dynamic sort:', params); | ||
return _.bind.apply(null, [self.redis.sort, self.redis].concat(params)); | ||
} | ||
return _.bind(self.redis.smembers, self.redis, setKey); | ||
@@ -373,0 +388,0 @@ }; |
var _ = require('lodash'); | ||
var debug = require('debug')('backbone-db-redis:query'); | ||
var utils = require('./utils'); | ||
@@ -64,4 +65,7 @@ function objToJSON(data) { | ||
debug('results:', results); | ||
if (err || !results) { | ||
return callback(err, []); | ||
var resultError = _.find(results, function(res) { | ||
return res.toString().indexOf('ERR') > -1; | ||
}); | ||
if (err || !results || resultError) { | ||
return callback(err || resultError, []); | ||
} | ||
@@ -108,7 +112,27 @@ var ids = results[self.idQueryNr]; | ||
var sortOpts = this._getSortSetKeyAndOrder(); | ||
if (sortOpts.sortKey) query.push(sortOpts.sortKey); | ||
var canUseIndexForSorting = false; | ||
// if sorting, check if we can use a set for sorting, otherwise try | ||
// to sort dynamically | ||
if (sortOpts.sortKey) { | ||
_.each(self.dbOptions.indexes, function(index) { | ||
var indexSet = self.db.getSortSetKey(self.model, index.property); | ||
if (indexSet === sortOpts.sortKey) canUseIndexForSorting = true; | ||
}); | ||
if (canUseIndexForSorting) { | ||
query.push(sortOpts.sortKey); | ||
} else { | ||
debug('no available index found for %s, try dynamic sort', sortOpts.sortKey); | ||
} | ||
} | ||
query.unshift(query.length); | ||
query.unshift(this.destinationSet); | ||
this.multi.zinterstore(query); | ||
this._fetchSorted(this.destinationSet); | ||
if (!canUseIndexForSorting && self.dbOptions.ModelClass.prototype.redis_type === 'hash') { | ||
this._sortUsingHash(this.destinationSet); | ||
} else { | ||
this._fetchSorted(this.destinationSet); | ||
} | ||
} else { | ||
@@ -177,2 +201,14 @@ this.idQueryNr = 0; | ||
_sortUsingHash: function(set, options) { | ||
var m = new this.dbOptions.ModelClass(); | ||
var idKey = this.db.getIdKey(m); | ||
var parsedSort = utils.parseSort(this.filterOptions.sort); | ||
var sortParams = idKey + ':*->' + parsedSort.sortProp; | ||
var params = [set, 'BY', sortParams]; | ||
if (parsedSort.sortOrder === -1) { | ||
params.push('DESC'); | ||
} | ||
this.multi.sort(params); | ||
}, | ||
_fetchSorted: function(set) { | ||
@@ -201,12 +237,7 @@ var self = this; | ||
if (!this.filterOptions.sort) return {}; | ||
var sortProp = this.filterOptions.sort; | ||
var sortOrder = 1; | ||
if (sortProp && sortProp[0] === '-') { | ||
sortOrder = -1; | ||
sortProp = sortProp.substr(1); | ||
} | ||
var sortKey = this.db.getSortSetKey(this.model, sortProp); | ||
var parsedSort = utils.parseSort(this.filterOptions.sort); | ||
var sortKey = this.db.getSortSetKey(this.model, parsedSort.sortProp); | ||
return { | ||
sortKey: sortKey, | ||
sortOrder: sortOrder | ||
sortOrder: parsedSort.sortOrder | ||
}; | ||
@@ -213,0 +244,0 @@ } |
{ | ||
"name": "backbone-db-redis", | ||
"version": "0.0.37", | ||
"version": "0.0.38", | ||
"description": "Redis driver for Backbone.Db", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -7,2 +7,3 @@ var _ = require('lodash'); | ||
var Collection = Promises.Collection; | ||
var nodefn = require('when/node/function'); | ||
var store = exports.store = new RedisDb('test'); | ||
@@ -73,2 +74,88 @@ var redis = require('redis'); | ||
var TestIndexedCollection = exports.TestIndexedCollection = MyCollection.extend({ | ||
indexDb: store, | ||
indexKey: 'test:i:Foo:relation', | ||
indexSort: function(model) { | ||
return model.get('score'); | ||
}, | ||
/** | ||
* Adds a new model to the index. Only specified attribute is indexed. | ||
* Db adapter returns a Promise | ||
*/ | ||
addToIndex: function(model, options) { | ||
options = options ? _.clone(options) : {}; | ||
if (!(model = this._prepareModel(model, options))) return false; | ||
if (!options.wait) this.add(model, options); | ||
return this._callAdapter('addToIndex', options, model); | ||
}, | ||
/** | ||
* Read model ids from the index. Populates collection models with ids, | ||
* for fetching from the main storage. | ||
*/ | ||
readFromIndex: function(options) { | ||
return this._callAdapter('readFromIndex', options); | ||
}, | ||
/** | ||
* Read from multiple indexes | ||
*/ | ||
readFromIndexes: function(options) { | ||
options = options ? _.clone(options) : {}; | ||
options.indexKeys = this.indexKeys || options.indexKeys; | ||
options.unionKey = this.unionKey || options.unionKey; | ||
if (this.indexKey) options.indexKeys.push(this.indexKey); | ||
var args = [this, options]; | ||
return nodefn.apply(_.bind(this.indexDb.readFromIndexes, this.indexDb), args); | ||
}, | ||
/** | ||
* Removes a model from index | ||
*/ | ||
removeFromIndex: function(models, options) { | ||
if(!models) return false; | ||
this.remove(models, options); | ||
var singular = !_.isArray(models); | ||
models = singular ? [models] : _.clone(models); | ||
return this._callAdapter('removeFromIndex', options, models); | ||
}, | ||
destroyAll: function(options) { | ||
return this._callAdapter('removeIndex', options); | ||
}, | ||
/** | ||
* Check if model exists in index | ||
*/ | ||
exists: function(model, options) { | ||
return this._callAdapter('existsInIndex', options, model); | ||
}, | ||
/** | ||
* Get count of items in index | ||
*/ | ||
count: function(options) { | ||
return this._callAdapter('indexCount', options); | ||
}, | ||
findKeys: function(keys, options) { | ||
options = options ? _.clone(options) : {}; | ||
options.keys = keys; | ||
return this._callAdapter('findKeys', options); | ||
}, | ||
_callAdapter: function(fn, options, models) { | ||
options = options ? _.clone(options) : {}; | ||
if (!this.indexDb) { | ||
throw new Error('indexDb must be defined'); | ||
} | ||
options.indexKey = this.indexKey; | ||
var args = [this, options]; | ||
if (models) args.splice(1, 0, models); | ||
return nodefn.apply(_.bind(this.indexDb[fn], this.indexDb), args); | ||
} | ||
}); | ||
exports.insertFixtureData = function (collection, cb) { | ||
@@ -75,0 +162,0 @@ var fns = []; |
require('chai').should(); | ||
var when = require('when'); | ||
var DB = require('../'); | ||
@@ -10,2 +11,3 @@ var Promises = require('backbone-promises'); | ||
var SetModel = require('../lib/set'); | ||
var setup = require('./setup'); | ||
@@ -34,3 +36,8 @@ var HashModel = Hash.extend({ | ||
} | ||
}); | ||
var HashIndexCollection = setup.TestIndexedCollection.extend({ | ||
model: HashModel, | ||
indexKey: 'test:i:hashindex', | ||
indexSort: null | ||
}); | ||
@@ -41,2 +48,3 @@ | ||
var collection; | ||
var index; | ||
@@ -49,2 +57,6 @@ before(function() { | ||
HashCollection.prototype.db = db; | ||
HashIndexCollection.prototype.sync = db.sync; | ||
HashIndexCollection.prototype.db = db; | ||
HashIndexCollection.prototype.indexDb = db; | ||
index = new HashIndexCollection(); | ||
}); | ||
@@ -56,2 +68,4 @@ | ||
collection.at(1).destroy(), | ||
collection.at(2).destroy(), | ||
index.destroyAll() | ||
]; | ||
@@ -89,10 +103,20 @@ return Promises.when.all(fns); | ||
e: new Date(), | ||
f: ['aa'] | ||
f: ['aa'], | ||
g: 'ddd' | ||
}; | ||
var data2 = { | ||
a: 125, | ||
a: 225, | ||
b: Math.random(), | ||
c: 'abc', | ||
d: true | ||
d: true, | ||
g: 'aaa' | ||
}; | ||
var data3 = { | ||
a: 125, | ||
b: Math.random(), | ||
c: 'gde', | ||
d: false, | ||
g: 'aaa' | ||
}; | ||
collection = new HashCollection(); | ||
@@ -102,2 +126,3 @@ var fns = [ | ||
collection.create(data2), | ||
collection.create(data3) | ||
]; | ||
@@ -107,3 +132,3 @@ return Promises.when | ||
.then(function() { | ||
collection.length.should.equal(2); | ||
collection.length.should.equal(3); | ||
}); | ||
@@ -139,3 +164,3 @@ }); | ||
var h = coll.at(0); | ||
h.get('a').should.equal(125); | ||
h.get('a').should.equal(225); | ||
var saveOpts = { | ||
@@ -150,6 +175,38 @@ inc: { | ||
}).then(function(){ | ||
h.get('a').should.equal(126); | ||
h.get('a').should.equal(226); | ||
}); | ||
}); | ||
}); | ||
it('should add models to index', function() { | ||
var coll = new HashCollection(); | ||
return coll | ||
.fetch() | ||
.then(function() { | ||
return when.all([ | ||
index.addToIndex(coll.at(0)), | ||
index.addToIndex(coll.at(1)), | ||
index.addToIndex(coll.at(2)) | ||
]); | ||
}); | ||
}); | ||
it('should read from index sorted by `a`', function() { | ||
return index | ||
.readFromIndex({sort: 'a'}) | ||
.then(function() { | ||
index.length.should.equal(3); | ||
index.at(0).get('a').should.equal(124); | ||
}); | ||
}); | ||
it('should read from index sorted by `-a`', function() { | ||
return index | ||
.readFromIndex({sort: '-a'}) | ||
.then(function() { | ||
index.length.should.equal(3); | ||
index.at(0).get('a').should.equal(226); | ||
}); | ||
}); | ||
}); |
var assert = require('assert'); | ||
var _ = require('lodash' ); | ||
var nodefn = require('when/node/function'); | ||
var Promises = require('backbone-promises'); | ||
@@ -11,89 +10,4 @@ var when = Promises.when; | ||
var store = setup.store; | ||
var TestCollection = setup.TestIndexedCollection; | ||
var TestCollection = MyCollection.extend({ | ||
indexDb: store, | ||
indexKey: 'test:i:Foo:relation', | ||
indexSort: function(model) { | ||
return model.get('score'); | ||
}, | ||
/** | ||
* Adds a new model to the index. Only specified attribute is indexed. | ||
* Db adapter returns a Promise | ||
*/ | ||
addToIndex: function(model, options) { | ||
options = options ? _.clone(options) : {}; | ||
if (!(model = this._prepareModel(model, options))) return false; | ||
if (!options.wait) this.add(model, options); | ||
return this._callAdapter('addToIndex', options, model); | ||
}, | ||
/** | ||
* Read model ids from the index. Populates collection models with ids, | ||
* for fetching from the main storage. | ||
*/ | ||
readFromIndex: function(options) { | ||
return this._callAdapter('readFromIndex', options); | ||
}, | ||
/** | ||
* Read from multiple indexes | ||
*/ | ||
readFromIndexes: function(options) { | ||
options = options ? _.clone(options) : {}; | ||
options.indexKeys = this.indexKeys || options.indexKeys; | ||
options.unionKey = this.unionKey || options.unionKey; | ||
if (this.indexKey) options.indexKeys.push(this.indexKey); | ||
var args = [this, options]; | ||
return nodefn.apply(_.bind(this.indexDb.readFromIndexes, this.indexDb), args); | ||
}, | ||
/** | ||
* Removes a model from index | ||
*/ | ||
removeFromIndex: function(models, options) { | ||
if(!models) return false; | ||
this.remove(models, options); | ||
var singular = !_.isArray(models); | ||
models = singular ? [models] : _.clone(models); | ||
return this._callAdapter('removeFromIndex', options, models); | ||
}, | ||
destroyAll: function(options) { | ||
return this._callAdapter('removeIndex', options); | ||
}, | ||
/** | ||
* Check if model exists in index | ||
*/ | ||
exists: function(model, options) { | ||
return this._callAdapter('existsInIndex', options, model); | ||
}, | ||
/** | ||
* Get count of items in index | ||
*/ | ||
count: function(options) { | ||
return this._callAdapter('indexCount', options); | ||
}, | ||
findKeys: function(keys, options) { | ||
options = options ? _.clone(options) : {}; | ||
options.keys = keys; | ||
return this._callAdapter('findKeys', options); | ||
}, | ||
_callAdapter: function(fn, options, models) { | ||
options = options ? _.clone(options) : {}; | ||
if (!this.indexDb) { | ||
throw new Error('indexDb must be defined'); | ||
} | ||
options.indexKey = this.indexKey; | ||
var args = [this, options]; | ||
if (models) args.splice(1, 0, models); | ||
return nodefn.apply(_.bind(this.indexDb[fn], this.indexDb), args); | ||
} | ||
}); | ||
var TestCollection2 = TestCollection.extend({ | ||
@@ -100,0 +14,0 @@ indexKey: 'test:i:Foo:relation2' |
65309
25
1930