Browse files

Merge pull request #17 from cboulanger/master

MongoDB backend now (almost) works
  • Loading branch information...
2 parents f809889 + 8fe71c6 commit d4e89b602b6808291176d99598ef83c78252e918 @manast manast committed Sep 26, 2012
Showing with 139 additions and 171 deletions.
  1. +0 −1 .gitignore
  2. +97 −150 lib/mongodb-backend.js
  3. +9 −5 test/acl_test.js
  4. +33 −15 test/run_tests.js
View
1 .gitignore
@@ -1,4 +1,3 @@
node_modules/*
.DS_Store
*~
-test/run_tests.js
View
247 lib/mongodb-backend.js
@@ -1,214 +1,161 @@
/**
- MongoDB Backend.
+ MongoDB Backend.
Implementation of the storage backend using MongoDB
*/
-var mongodb = require('mongodb');
var contract = require('./contract');
var async = require('async');
var _ = require('underscore');
-function isDeadlocked(cb){
- cb.__deferCount = (cb.__deferCount || 0) +1;
- return cb.__deferCount>10;
-}
-
-function MongoDBBackend(client, collectionName){
- this.client = client;
- collectionName = collectionName||"acl";
- this.collection = new mongodb.Collection(client, collectionName);
- this.transactions = new mongodb.Collection(client, collectionName+"-transactions");
+function MongoDBBackend(db, prefix){
+ this.db = db;
}
MongoDBBackend.prototype = {
- /**
+ /**
Begins a transaction.
*/
begin : function(){
// returns a transaction object(just an array of functions will do here.)
return [];
},
-
+
/**
Ends a transaction (and executes it)
*/
end : function(transaction, cb){
- contract(arguments).params('array', 'function').end();
-
- var self = this;
- self.transactions.find().toArray(function(err,docs){
- // if a transaction is in progress, wait
- if(docs.length){
- console.log("transaction in progress while starting new transaction, deferring...");
- return setTimeout(function(){
- if(isDeadlocked(cb)) return cb(new Error("Cannot start new transaction, deadlock"));
- self.end(transaction,cb);
- },0);
- }
- var transactionId = ("" + Math.random()).replace(".","");
- self.transactions.save({transactionId:transactionId},{save:true},function(err,doc){
- // Execute transactions serially, remove transaction doc and return
- async.series(transaction,function(err,results){
- self.transactions.remove({"_id":doc._id});
- cb(err);
- });
- });
+ contract(arguments).params('array', 'function').end();
+ async.series(transaction,function(err){
+ cb(err instanceof Error? err : undefined);
});
},
-
+
/**
Cleans the whole storage.
*/
clean : function(cb){
contract(arguments).params('function').end();
- var self = this;
- this.collection.remove(null, {safe:true}, function(err){
- self.transactions.remove(null, {safe:true}, function(err){
- cb(err);
- });
- });
+ this.db.collections(function(err, collections) {
+ if (err instanceof Error) return cb(err);
+ async.forEach(collections,function(coll,innercb){
+ coll.drop(function(){innercb()}); // ignores errors
+ },cb);
+ });
},
-
+
/**
Gets the contents at the bucket's key.
*/
get : function(bucket, key, cb){
- contract(arguments)
- .params('string', 'string', 'function')
- .end();
-
- var self=this;
- self.transactions.find().toArray(function(err,docs){
- // if a transaction is in progress, wait
- if(docs.length){
- console.log("transaction in progress while trying to get(), deferring...");
- return setTimeout(function(){
- if(isDeadlocked(cb)) return cb(new Error("Cannot get(), deadlock"));
- self.get(bucket, key, cb);
- },0);
- }
- // otherwise, find the document
- self.collection.findOne(
- {bucket:bucket,key:key},
- function(err, doc){
- if(err) return cb(err);
- if(!doc) return cb(undefined,[]);
- cb(null,doc.value||[]);
- }
- );
+ contract(arguments)
+ .params('string', 'string', 'function')
+ .end();
+ this.db.collection(bucket,function(err,collection){
+ if(err instanceof Error) return cb(err);
+ collection.findOne({key:key},function(err, doc){
+ if(err) return cb(err);
+ if(! _.isObject(doc) ) return cb(undefined,[]);
+ cb(undefined,_.without(_.keys(doc),"key","_id"));
+ });
});
},
- /**
- Returns the union of the values in the given keys.
- */
+ /**
+ Returns the union of the values in the given keys.
+ */
union : function(bucket, keys, cb){
contract(arguments)
- .params('string', 'array', 'function')
- .end();
-
- var self=this;
- self.transactions.find().toArray(function(err,docs){
- // if a transaction is in progress, wait
- if(docs.length){
- console.log("transaction in progress while trying to union(), deferring...");
- return setTimeout(function(){
- if(isDeadlocked(cb)) return cb(new Error("Cannot union(), deadlock"));
- self.union(bucket, keys, cb);
- },0);
- }
- // otherwise, find the document
- self.collection.find(
- {bucket: bucket, key: { $in: keys }},
- {fields:{value:1}}
- ).toArray(function(err,docs){
- if(err) return cb(err);
+ .params('string', 'array', 'function')
+ .end();
+
+ this.db.collection(bucket,function(err,collection){
+ if(err instanceof Error) return cb(err);
+ collection.find({key: { $in: keys }}).toArray(function(err,docs){
+ if(err instanceof Error) return cb(err);
if( ! docs.length ) return cb(undefined, []);
var keyArrays = [];
docs.forEach(function(doc){
- keyArrays.push.apply(keyArrays, doc.value);
+ keyArrays.push.apply(keyArrays, _.keys(doc));
});
- cb(undefined, _.union(keyArrays));
+ cb(undefined, _.without(_.union(keyArrays),"key","_id"));
});
});
- },
-
+ },
+
/**
- Adds values to a given key inside a bucket.
- */
- add : function(transaction, bucket, key, values){
- contract(arguments)
- .params('array', 'string', 'string','string|array')
+ Adds values to a given key inside a bucket.
+ */
+ add : function(transaction, bucket, key, values){
+ contract(arguments)
+ .params('array', 'string', 'string','string|array')
.end();
-
- values = makeArray(values);
- var collection = this.collection;
- console.log("Adding %s to %s/%s",values,bucket,key);
+
+ if(key=="key") throw new Error("Key name 'key' is not allowed.");
+ var self=this;
transaction.push(function(cb){
- collection.findOne({bucket:bucket,key:key},function(err,doc){
- var oldValue = doc? doc.value:[];
- var newValue = _.union(oldValue,values);
- collection.update(
- {bucket:bucket,key:key},
- {$set:{value:newValue}},
- {safe:true,upsert:true},
- function(err){
- console.log(" Added %s to %s/%s: %s -> %s",values,bucket,key,oldValue,newValue);
- cb(err);
- }
- );
+ values = makeArray(values);
+ self.db.collection(bucket, function(err,collection){
+ if(err instanceof Error) return cb(err);
+
+ // build doc from array values
+ var doc = {};
+ values.forEach(function(value){doc[value]=true;});
+
+ // update document
+ collection.update({key:key},{$set:doc},{safe:true,upsert:true},function(err){
+ if(err instanceof Error) return cb(err);
+ cb(undefined);
+ });
});
});
- },
-
+ },
+
/**
Delete the given key(s) at the bucket
*/
del : function(transaction, bucket, keys){
- contract(arguments)
- .params('array', 'string', 'string|array')
- .end();
+ contract(arguments)
+ .params('array', 'string', 'string|array')
+ .end();
keys = makeArray(keys);
- var collection = this.collection;
+ var self= this;
transaction.push(function(cb){
- collection.remove(
- {bucket:bucket,key:{$in:keys}},
- {safe:true},
- function(err){cb(err);}
- );
- })
+ self.db.collection(bucket,function(err,collection){
+ if(err instanceof Error) return cb(err);
+ collection.remove({key:{$in:keys}},{safe:true},function(err){
+ if(err instanceof Error) return cb(err);
+ cb(undefined);
+ });
+ });
+ });
},
-
- /**
- Removes values from a given key inside a bucket.
- */
- remove : function(transaction, bucket, key, values){
- contract(arguments)
- .params('array', 'string', 'string','string|array')
+
+ /**
+ Removes values from a given key inside a bucket.
+ */
+ remove : function(transaction, bucket, key, values){
+ contract(arguments)
+ .params('array', 'string', 'string','string|array')
.end();
-
- var collection = this.collection;
+
+ var self=this;
values = makeArray(values);
- console.log("Removing %s from %s/%s...",values, bucket, key );
transaction.push(function(cb){
- collection.findOne({bucket:bucket,key:key}, function(err, doc){
- if(err) return cb(err);
- var oldValue = doc? doc.value:[];
- var newValue = _.difference(oldValue, values);
- collection.update(
- {bucket:bucket,key:key},
- {$set:{value: newValue }},
- {safe:true,upsert:true},
- function(err){
- collection.findOne({bucket:bucket,key:key}, function(err, doc){
- console.log(" Removed %s from %s/%s Result:%s -> %s", values,bucket,key, oldValue, doc.value);
- cb(err);
- });
- }
- );
+ self.db.collection(bucket,function(err,collection){
+ if(err instanceof Error) return cb(err);
+
+ // build doc from array values
+ var doc = {};
+ values.forEach(function(value){doc[value]=true;});
+
+ // update document
+ collection.update({key:key},{$unset:doc},{safe:true,upsert:true},function(err){
+ if(err instanceof Error) return cb(err);
+ cb(undefined);
+ });
});
});
- },
+ }
}
function makeArray(arr){
View
14 test/acl_test.js
@@ -9,21 +9,25 @@ function testBackend(type, options, cb){
{
case "memory":
var memoryBackend = require('../lib/memory-backend');
- console.log("Starting Memory Backend tests");
startTests(new memoryBackend(), cb);
break;
case "redis":
var redisBackend = require('../lib/redis-backend');
- var client = require('redis').createClient(options.port, options.host );
- startTests(new redisBackend(client), cb);
+ var client = require('redis').createClient(
+ options.port, options.host, {no_ready_check: true} );
+ var start = function(){
+ startTests(new redisBackend(client), cb);
+ };
+ if (options.password) client.auth(options.password, start);
+ else start();
break;
case "mongodb":
var mongodb = require('mongodb');
var mongoDBBackend = require('../lib/mongodb-backend');
- mongodb.Db.connect(options, function(error, client) {
- startTests(new mongoDBBackend(client, "acl"), cb);
+ mongodb.connect(options,function(error, db) {
+ startTests(new mongoDBBackend(db, "acl"), cb);
});
break;
View
48 test/run_tests.js
@@ -1,21 +1,39 @@
+var testBackend = require('./acl_test').testBackend,
+ async = require('async');
-var testBackend = require('./acl_test').testBackend;
+var tests = [
-/* memory */
-testBackend("memory", {}, function(results){
- var exitCode = results.honored === results.total ? 0 : -1;
- exitCode && process.exit(exitCode);
+ /* Memory */
+ function(cb) {
+ console.log("Testing memory backend");
+ testBackend("memory", {}, cb);
+ },
- /* redis */
- testBackend("redis", { host:'127.0.0.1', port:6379}, function(results){
+ /* Redis */
+ function(cb) {
+ console.log("Testing Redis backend");
+ var options = {
+ host: '127.0.0.1',
+ port: 6379,
+ password: null
+ };
+ testBackend("redis", options, cb);
+ },
+
+ /* MongoDB */
+ function(cb) {
+ console.log("Testing MongoDB backend");
+ var url = "mongodb://127.0.0.1:27017/acltest";
+ testBackend("mongodb", url, cb);
+ }
+
+];
+
+// run tests
+async.forEachSeries(tests, function(test, cb) {
+ test(function(results) {
var exitCode = results.honored === results.total ? 0 : -1;
exitCode && process.exit(exitCode);
-
- /* MongoDB */
- //testBackend("mongodb", "mongodb://127.0.0.1:27017/acltest");
- testBackend("mongodb", "mongodb://127.0.0.1:27017/acltest", function(results){
- var exitCode = results.honored === results.total ? 0 : -1;
- exitCode && process.exit(exitCode);
- })
+ cb();
});
-});
+});

0 comments on commit d4e89b6

Please sign in to comment.