Redis indexes intersections
This commit is contained in:
parent
5911ed34bb
commit
c5da2b56a7
28
lib/redis.js
28
lib/redis.js
|
@ -58,7 +58,9 @@ BridgeToRedis.prototype.updateIndexes = function (model, data, callback) {
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
if (schedule.length) {
|
if (schedule.length) {
|
||||||
this.client.multi(schedule).exec(callback);
|
this.client.multi(schedule).exec(function (err) {
|
||||||
|
callback(err);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
callback(null);
|
callback(null);
|
||||||
}
|
}
|
||||||
|
@ -100,13 +102,13 @@ BridgeToRedis.prototype.destroy = function destroy(model, id, callback) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
BridgeToRedis.prototype.possibleIndex = function possibleIndex(model, filter) {
|
BridgeToRedis.prototype.possibleIndexes = function (model, filter) {
|
||||||
if (!filter || Object.keys(filter).length === 0) return false;
|
if (!filter || Object.keys(filter).length === 0) return false;
|
||||||
|
|
||||||
var foundIndex = false;
|
var foundIndex = [];
|
||||||
Object.keys(filter).forEach(function (key) {
|
Object.keys(filter).forEach(function (key) {
|
||||||
if (!foundIndex && this.indexes[model][key]) {
|
if (this.indexes[model][key]) {
|
||||||
foundIndex = key;
|
foundIndex.push('i:' + model + ':' + key + ':' + filter[key]);
|
||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
|
@ -115,26 +117,24 @@ BridgeToRedis.prototype.possibleIndex = function possibleIndex(model, filter) {
|
||||||
|
|
||||||
BridgeToRedis.prototype.all = function all(model, filter, callback) {
|
BridgeToRedis.prototype.all = function all(model, filter, callback) {
|
||||||
var ts = Date.now();
|
var ts = Date.now();
|
||||||
|
var client = this.client;
|
||||||
|
|
||||||
var index = this.possibleIndex(model, filter);
|
var indexes = this.possibleIndexes(model, filter);
|
||||||
if (false) {
|
if (indexes.length) {
|
||||||
// console.log('using index!', filter);
|
indexes.push(handleKeys);
|
||||||
this.client.smembers('i:' + model + ':' + index + ':' + filter[index],
|
client.sinter.apply(client, indexes);
|
||||||
handleKeys.bind(this));
|
|
||||||
} else {
|
} else {
|
||||||
// console.log('without index', filter);
|
client.keys(model + ':*', handleKeys);
|
||||||
this.client.keys(model + ':*', handleKeys.bind(this));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleKeys(err, keys) {
|
function handleKeys(err, keys) {
|
||||||
// console.log(arguments);
|
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err, []);
|
return callback(err, []);
|
||||||
}
|
}
|
||||||
var query = keys.map(function (key) {
|
var query = keys.map(function (key) {
|
||||||
return ['hgetall', key];
|
return ['hgetall', key];
|
||||||
});
|
});
|
||||||
this.client.multi(query).exec(function (err, replies) {
|
client.multi(query).exec(function (err, replies) {
|
||||||
// console.log('Redis time: %dms', Date.now() - ts);
|
// console.log('Redis time: %dms', Date.now() - ts);
|
||||||
callback(err, filter ? replies.filter(applyFilter(filter)) : replies);
|
callback(err, filter ? replies.filter(applyFilter(filter)) : replies);
|
||||||
});
|
});
|
||||||
|
|
|
@ -27,12 +27,12 @@ Object.keys(schemas).forEach(function (schemaName) {
|
||||||
|
|
||||||
function testOrm(schema) {
|
function testOrm(schema) {
|
||||||
|
|
||||||
var Post;
|
var Post, User;
|
||||||
var start = Date.now();
|
var start = Date.now();
|
||||||
|
|
||||||
it('should define class', function (test) {
|
it('should define class', function (test) {
|
||||||
|
|
||||||
var User = schema.define('User', {
|
User = schema.define('User', {
|
||||||
name: String,
|
name: String,
|
||||||
bio: Text,
|
bio: Text,
|
||||||
approved: Boolean,
|
approved: Boolean,
|
||||||
|
@ -47,6 +47,18 @@ function testOrm(schema) {
|
||||||
published: { type: Boolean, default: false }
|
published: { type: Boolean, default: false }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
User.hasMany(Post, {as: 'posts', foreignKey: 'userId'});
|
||||||
|
// creates instance methods:
|
||||||
|
// user.posts(conds)
|
||||||
|
// user.buildPost(data) // like new Post({userId: user.id});
|
||||||
|
// user.createPost(data) // build and save
|
||||||
|
|
||||||
|
Post.belongsTo(User, {as: 'author', foreignKey: 'userId'});
|
||||||
|
// creates instance methods:
|
||||||
|
// post.author(callback) -- getter when called with function
|
||||||
|
// post.author() -- sync getter when called without params
|
||||||
|
// post.author(user) -- setter when called with object
|
||||||
|
|
||||||
var user = new User;
|
var user = new User;
|
||||||
|
|
||||||
test.ok(User instanceof Function);
|
test.ok(User instanceof Function);
|
||||||
|
@ -82,7 +94,7 @@ function testOrm(schema) {
|
||||||
|
|
||||||
it('should be expoted to JSON', function (test) {
|
it('should be expoted to JSON', function (test) {
|
||||||
test.equal(JSON.stringify(new Post({id: 1, title: 'hello, json'})),
|
test.equal(JSON.stringify(new Post({id: 1, title: 'hello, json'})),
|
||||||
'{"id":1,"title":"hello, json","content":null,"date":null,"published":null}');
|
'{"id":1,"title":"hello, json","content":null,"date":null,"published":null,"userId":null}');
|
||||||
test.done();
|
test.done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -260,6 +272,23 @@ function testOrm(schema) {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should handle hasMany relationship', function (test) {
|
||||||
|
User.create(function (err, u) {
|
||||||
|
if (err) return console.log(err);
|
||||||
|
test.ok(u.posts, 'Method defined: posts');
|
||||||
|
test.ok(u.buildPost, 'Method defined: buildPost');
|
||||||
|
test.ok(u.createPost, 'Method defined: createPost');
|
||||||
|
u.createPost(function (err, post) {
|
||||||
|
if (err) return console.log(err);
|
||||||
|
test.ok(post.author(), u.id);
|
||||||
|
u.posts(function (err, posts) {
|
||||||
|
test.strictEqual(posts.pop(), post);
|
||||||
|
test.done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should destroy all records', function (test) {
|
it('should destroy all records', function (test) {
|
||||||
Post.destroyAll(function (err) {
|
Post.destroyAll(function (err) {
|
||||||
Post.all(function (err, posts) {
|
Post.all(function (err, posts) {
|
||||||
|
|
|
@ -1,317 +0,0 @@
|
||||||
require('./spec_helper').init(exports);
|
|
||||||
|
|
||||||
[ 'redis~'
|
|
||||||
, 'mysql'
|
|
||||||
, 'mongodb~'
|
|
||||||
, 'postgres~'
|
|
||||||
].forEach(function (driver) {
|
|
||||||
// context(driver, testCasesFor(driver));
|
|
||||||
});
|
|
||||||
|
|
||||||
function testCasesFor (driver) {
|
|
||||||
return function () {
|
|
||||||
|
|
||||||
function Post () { this.initialize.apply(this, Array.prototype.slice.call(arguments)); }
|
|
||||||
function Comment () { this.initialize.apply(this, Array.prototype.slice.call(arguments)); }
|
|
||||||
|
|
||||||
var properties = {};
|
|
||||||
|
|
||||||
properties['post'] = {
|
|
||||||
title: { type: String, validate: /.{10,255}/ },
|
|
||||||
content: { type: String },
|
|
||||||
published: { type: Boolean, default: false },
|
|
||||||
date: { type: Date, default: function () {return new Date} }
|
|
||||||
};
|
|
||||||
|
|
||||||
properties['comment'] = {
|
|
||||||
content: { type: String, validate: /./ },
|
|
||||||
date: { type: Date },
|
|
||||||
author: { type: String },
|
|
||||||
approved: { type: Boolean }
|
|
||||||
};
|
|
||||||
|
|
||||||
var associations = {};
|
|
||||||
associations['post'] = {
|
|
||||||
comments: {className: 'Comment', relationType: 'n', tableName: 'comment'}
|
|
||||||
};
|
|
||||||
|
|
||||||
associations['comment'] = {
|
|
||||||
post: {className: 'Post', relationType: '<', tableName: 'post'}
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
var orm = require('../lib/datamapper/' + driver);
|
|
||||||
if (driver == 'mysql') {
|
|
||||||
orm.configure({
|
|
||||||
host: 'webdesk.homelinux.org',
|
|
||||||
port: 3306,
|
|
||||||
database: 'test',
|
|
||||||
user: 'guest',
|
|
||||||
password: ''
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e.message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
orm.debugMode = true;
|
|
||||||
orm.mixPersistMethods(Post, {
|
|
||||||
className: 'Post',
|
|
||||||
tableName: 'post',
|
|
||||||
properties: properties['post'],
|
|
||||||
associations: associations['post']
|
|
||||||
});
|
|
||||||
orm.mixPersistMethods(Comment, {
|
|
||||||
className: 'Comment',
|
|
||||||
tableName: 'comment',
|
|
||||||
properties: properties['comment'],
|
|
||||||
associations: associations['comment'],
|
|
||||||
scopes: {
|
|
||||||
approved: { conditions: { approved: true } },
|
|
||||||
author: { block: function (author) { return {conditions: {author: author}}; } }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var HOW_MANY_RECORDS = 1;
|
|
||||||
|
|
||||||
it('cleanup database', function (test) {
|
|
||||||
var wait = 0;
|
|
||||||
var time = new Date;
|
|
||||||
var len;
|
|
||||||
Post.allInstances(function (posts) {
|
|
||||||
if (posts.length === 0) test.done();
|
|
||||||
len = posts.length;
|
|
||||||
posts.forEach(function (post) {
|
|
||||||
wait += 1;
|
|
||||||
post.destroy(done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function done () {
|
|
||||||
if (--wait === 0) {
|
|
||||||
test.done();
|
|
||||||
console.log('Cleanup %d records completed in %d ms', len, new Date - time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('create a lot of data', function (test) {
|
|
||||||
var wait = HOW_MANY_RECORDS;
|
|
||||||
var time = new Date;
|
|
||||||
for (var i = wait; i > 0; i -= 1) {
|
|
||||||
Post.create({title: Math.random().toString(), content: arguments.callee.caller.toString(), date: new Date, published: false}, done);
|
|
||||||
}
|
|
||||||
|
|
||||||
function done () {
|
|
||||||
if (--wait === 0) {
|
|
||||||
test.done();
|
|
||||||
console.log('Creating %d records completed in %d ms', HOW_MANY_RECORDS, new Date - time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should retrieve all data fast', function (test) {
|
|
||||||
var time = new Date;
|
|
||||||
Post.allInstances(function (posts) {
|
|
||||||
test.equal(posts.length, HOW_MANY_RECORDS);
|
|
||||||
console.log('Retrieving %d records completed in %d ms', HOW_MANY_RECORDS, new Date - time);
|
|
||||||
test.done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should initialize object properly', function (test) {
|
|
||||||
var hw = 'Hello world', post = new Post({title: hw});
|
|
||||||
test.equal(post.title, hw);
|
|
||||||
test.ok(!post.propertyChanged('title'));
|
|
||||||
post.title = 'Goodbye, Lenin';
|
|
||||||
test.equal(post.title_was, hw);
|
|
||||||
test.ok(post.propertyChanged('title'));
|
|
||||||
test.ok(post.isNewRecord());
|
|
||||||
test.done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create object', function (test) {
|
|
||||||
Post.create(function () {
|
|
||||||
test.ok(this.id);
|
|
||||||
Post.exists(this.id, function (exists) {
|
|
||||||
test.ok(exists);
|
|
||||||
test.done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should save object', function (test) {
|
|
||||||
var title = 'Initial title', title2 = 'Hello world',
|
|
||||||
date = new Date;
|
|
||||||
|
|
||||||
Post.create({
|
|
||||||
title: title,
|
|
||||||
date: date
|
|
||||||
}, function () {
|
|
||||||
test.ok(this.id);
|
|
||||||
test.equals(this.title, title);
|
|
||||||
test.equals(this.date, date);
|
|
||||||
this.title = title2;
|
|
||||||
this.save(function () {
|
|
||||||
test.equal(this.title, title2);
|
|
||||||
test.ok(!this.propertyChanged('title'));
|
|
||||||
test.done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create object with initial data', function (test) {
|
|
||||||
var title = 'Initial title',
|
|
||||||
date = new Date;
|
|
||||||
|
|
||||||
Post.create({
|
|
||||||
title: title,
|
|
||||||
date: date
|
|
||||||
}, function () {
|
|
||||||
test.ok(this.id);
|
|
||||||
test.equals(this.title, title);
|
|
||||||
test.equals(this.date, date);
|
|
||||||
Post.find(this.id, function () {
|
|
||||||
test.equal(this.title, title);
|
|
||||||
test.equal(this.date, date.toString());
|
|
||||||
test.done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not create new instances for the same object', function (test) {
|
|
||||||
var title = 'Initial title';
|
|
||||||
Post.create({ title: title }, function () {
|
|
||||||
var post = this;
|
|
||||||
test.ok(this.id, 'Object should have id');
|
|
||||||
test.equals(this.title, title);
|
|
||||||
Post.find(this.id, function () {
|
|
||||||
test.equal(this.title, title);
|
|
||||||
test.strictEqual(this, post);
|
|
||||||
test.done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should destroy object', function (test) {
|
|
||||||
Post.create(function () {
|
|
||||||
var post = this;
|
|
||||||
Post.exists(post.id, function (exists) {
|
|
||||||
test.ok(exists, 'Object exists');
|
|
||||||
post.destroy(function () {
|
|
||||||
Post.exists(post.id, function (exists) {
|
|
||||||
test.ok(!exists, 'Object not exists');
|
|
||||||
Post.find(post.id, function (err, obj) {
|
|
||||||
test.ok(err, 'Object not found');
|
|
||||||
test.equal(obj, null, 'Param obj should be null');
|
|
||||||
test.done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update single attribute', function (test) {
|
|
||||||
Post.create({title: 'title', content: 'content'}, function () {
|
|
||||||
this.content = 'New content';
|
|
||||||
this.updateAttribute('title', 'New title', function () {
|
|
||||||
test.equal(this.title, 'New title');
|
|
||||||
test.ok(!this.propertyChanged('title'));
|
|
||||||
test.equal(this.content, 'New content');
|
|
||||||
test.ok(this.propertyChanged('content'));
|
|
||||||
this.reload(function () {
|
|
||||||
test.equal(this.title, 'New title');
|
|
||||||
test.ok(!this.propertyChanged('title'));
|
|
||||||
test.equal(this.content, 'content');
|
|
||||||
test.ok(!this.propertyChanged('content'));
|
|
||||||
test.done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// NOTE: this test rely on previous
|
|
||||||
it('should fetch collection', function (test) {
|
|
||||||
Post.allInstances(function (posts) {
|
|
||||||
test.ok(posts.length > 0);
|
|
||||||
test.strictEqual(posts[0].constructor, Post);
|
|
||||||
test.done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// NOTE: this test rely on previous
|
|
||||||
it('should fetch first, second, third and last elements of class', function (test) {
|
|
||||||
test.done(); return;
|
|
||||||
var queries = 4;
|
|
||||||
test.expect(queries);
|
|
||||||
function done () { if (--queries == 0) test.done(); }
|
|
||||||
|
|
||||||
Post.first(function (post) {
|
|
||||||
test.strictEqual(post.constructor, Post);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
Post.second(function (post) {
|
|
||||||
test.strictEqual(post.constructor, Post);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
Post.third(function (post) {
|
|
||||||
test.strictEqual(post.constructor, Post);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
Post.last(function (post) {
|
|
||||||
test.strictEqual(post.constructor, Post);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should load associated collection', function (test) {
|
|
||||||
test.done(); return;
|
|
||||||
Post.last(function (post) {
|
|
||||||
post.comments.approved.where('author = ?', 'me').load();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should find record and associated association', function (test) {
|
|
||||||
test.done(); return;
|
|
||||||
Post.last(function (post) {
|
|
||||||
Post.find(post.id, {include: 'comments'}, function () {
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fetch associated collection', function (test) {
|
|
||||||
test.done(); return;
|
|
||||||
Post.create(function () {
|
|
||||||
// load collection
|
|
||||||
this.comments(function () {
|
|
||||||
});
|
|
||||||
// creating associated object
|
|
||||||
this.comments.create(function () {
|
|
||||||
});
|
|
||||||
this.comments.build().save();
|
|
||||||
// named scopes
|
|
||||||
this.comments.pending(function () {
|
|
||||||
});
|
|
||||||
this.comments.approved(function () {
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should validate object', function (test) {
|
|
||||||
test.done(); return;
|
|
||||||
var post = new Post;
|
|
||||||
test.ok(!post.isValid());
|
|
||||||
post.save(function (id) {
|
|
||||||
test.ok(!id, 'Post should not be saved');
|
|
||||||
});
|
|
||||||
post.title = 'Title';
|
|
||||||
test.ok(post.isValid());
|
|
||||||
post.save(function (id) {
|
|
||||||
test.ok(id);
|
|
||||||
test.done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -14,8 +14,8 @@ schemas =
|
||||||
testOrm = (schema) ->
|
testOrm = (schema) ->
|
||||||
|
|
||||||
User = Post = 'unknown'
|
User = Post = 'unknown'
|
||||||
maxUsers = 50
|
maxUsers = 100
|
||||||
maxPosts = 10000
|
maxPosts = 50000
|
||||||
users = []
|
users = []
|
||||||
|
|
||||||
it 'should define simple', (test) ->
|
it 'should define simple', (test) ->
|
||||||
|
@ -28,12 +28,11 @@ testOrm = (schema) ->
|
||||||
age: Number
|
age: Number
|
||||||
}
|
}
|
||||||
|
|
||||||
Post = schema.define('Post', {
|
Post = schema.define 'Post',
|
||||||
title: { type: String, length: 255 }
|
title: { type: String, length: 255, index: true }
|
||||||
content: { type: Text }
|
content: { type: Text }
|
||||||
date: { type: Date, detault: Date.now }
|
date: { type: Date, detault: Date.now }
|
||||||
published: { type: Boolean, default: false }
|
published: { type: Boolean, default: false }
|
||||||
})
|
|
||||||
|
|
||||||
User.hasMany(Post, {as: 'posts', foreignKey: 'userId'})
|
User.hasMany(Post, {as: 'posts', foreignKey: 'userId'})
|
||||||
Post.belongsTo(User, {as: 'author', foreignKey: 'userId'})
|
Post.belongsTo(User, {as: 'author', foreignKey: 'userId'})
|
||||||
|
@ -52,8 +51,8 @@ testOrm = (schema) ->
|
||||||
done = -> test.done() if --wait == 0
|
done = -> test.done() if --wait == 0
|
||||||
rnd = (title) ->
|
rnd = (title) ->
|
||||||
{
|
{
|
||||||
userId: users[Math.floor(Math.random() * maxUsers)].id,
|
userId: users[Math.floor(Math.random() * maxUsers)].id
|
||||||
title: title
|
title: 'Post number ' + (title % 5)
|
||||||
}
|
}
|
||||||
Post.create(rnd(num), done) for num in [1..maxPosts]
|
Post.create(rnd(num), done) for num in [1..maxPosts]
|
||||||
|
|
||||||
|
@ -62,10 +61,12 @@ testOrm = (schema) ->
|
||||||
done = -> test.done() if --wait == 0
|
done = -> test.done() if --wait == 0
|
||||||
ts = Date.now()
|
ts = Date.now()
|
||||||
query = (num) ->
|
query = (num) ->
|
||||||
users[num].posts (err, collection) ->
|
users[num].posts { title: 'Post number 3' }, (err, collection) ->
|
||||||
console.log('User ' + num + ':', collection.length, 'posts in', Date.now() - ts,'ms')
|
console.log('User ' + num + ':', collection.length, 'posts in', Date.now() - ts,'ms')
|
||||||
done()
|
done()
|
||||||
query num for num in [1..4]
|
query num for num in [0..4]
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
it 'should destroy all data', (test) ->
|
it 'should destroy all data', (test) ->
|
||||||
Post.destroyAll ->
|
Post.destroyAll ->
|
||||||
|
|
Loading…
Reference in New Issue