Skip to content
Browse files

Redis indexes intersections

  • Loading branch information...
1 parent 5911ed3 commit c5da2b56a759fd896fcb5a955cf121ffece6b3a4 @1602 committed
Showing with 56 additions and 343 deletions.
  1. +14 −14 lib/redis.js
  2. +32 −3 test/common_test.js
  3. +0 −317 test/datamapper_test.js
  4. +10 −9 test/perf_test.coffee
View
28 lib/redis.js
@@ -58,7 +58,9 @@ BridgeToRedis.prototype.updateIndexes = function (model, data, callback) {
}.bind(this));
if (schedule.length) {
- this.client.multi(schedule).exec(callback);
+ this.client.multi(schedule).exec(function (err) {
+ callback(err);
+ });
} else {
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;
- var foundIndex = false;
+ var foundIndex = [];
Object.keys(filter).forEach(function (key) {
- if (!foundIndex && this.indexes[model][key]) {
- foundIndex = key;
+ if (this.indexes[model][key]) {
+ foundIndex.push('i:' + model + ':' + key + ':' + filter[key]);
}
}.bind(this));
@@ -115,26 +117,24 @@ BridgeToRedis.prototype.possibleIndex = function possibleIndex(model, filter) {
BridgeToRedis.prototype.all = function all(model, filter, callback) {
var ts = Date.now();
+ var client = this.client;
- var index = this.possibleIndex(model, filter);
- if (false) {
- // console.log('using index!', filter);
- this.client.smembers('i:' + model + ':' + index + ':' + filter[index],
- handleKeys.bind(this));
+ var indexes = this.possibleIndexes(model, filter);
+ if (indexes.length) {
+ indexes.push(handleKeys);
+ client.sinter.apply(client, indexes);
} else {
- // console.log('without index', filter);
- this.client.keys(model + ':*', handleKeys.bind(this));
+ client.keys(model + ':*', handleKeys);
}
function handleKeys(err, keys) {
- // console.log(arguments);
if (err) {
return callback(err, []);
}
var query = keys.map(function (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);
callback(err, filter ? replies.filter(applyFilter(filter)) : replies);
});
View
35 test/common_test.js
@@ -27,12 +27,12 @@ Object.keys(schemas).forEach(function (schemaName) {
function testOrm(schema) {
- var Post;
+ var Post, User;
var start = Date.now();
it('should define class', function (test) {
- var User = schema.define('User', {
+ User = schema.define('User', {
name: String,
bio: Text,
approved: Boolean,
@@ -47,6 +47,18 @@ function testOrm(schema) {
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;
test.ok(User instanceof Function);
@@ -82,7 +94,7 @@ function testOrm(schema) {
it('should be expoted to JSON', function (test) {
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();
});
@@ -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) {
Post.destroyAll(function (err) {
Post.all(function (err, posts) {
View
317 test/datamapper_test.js
@@ -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();
- });
- });
-
- }
-};
View
19 test/perf_test.coffee
@@ -14,8 +14,8 @@ schemas =
testOrm = (schema) ->
User = Post = 'unknown'
- maxUsers = 50
- maxPosts = 10000
+ maxUsers = 100
+ maxPosts = 50000
users = []
it 'should define simple', (test) ->
@@ -28,12 +28,11 @@ testOrm = (schema) ->
age: Number
}
- Post = schema.define('Post', {
- title: { type: String, length: 255 }
+ Post = schema.define 'Post',
+ title: { type: String, length: 255, index: true }
content: { type: Text }
date: { type: Date, detault: Date.now }
published: { type: Boolean, default: false }
- })
User.hasMany(Post, {as: 'posts', foreignKey: 'userId'})
Post.belongsTo(User, {as: 'author', foreignKey: 'userId'})
@@ -52,8 +51,8 @@ testOrm = (schema) ->
done = -> test.done() if --wait == 0
rnd = (title) ->
{
- userId: users[Math.floor(Math.random() * maxUsers)].id,
- title: title
+ userId: users[Math.floor(Math.random() * maxUsers)].id
+ title: 'Post number ' + (title % 5)
}
Post.create(rnd(num), done) for num in [1..maxPosts]
@@ -62,10 +61,12 @@ testOrm = (schema) ->
done = -> test.done() if --wait == 0
ts = Date.now()
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')
done()
- query num for num in [1..4]
+ query num for num in [0..4]
+
+ return
it 'should destroy all data', (test) ->
Post.destroyAll ->

0 comments on commit c5da2b5

Please sign in to comment.
Something went wrong with that request. Please try again.