Skip to content

Commit

Permalink
Added cassandra backend
Browse files Browse the repository at this point in the history
  • Loading branch information
alonl committed Apr 26, 2015
1 parent 3902eb0 commit 9481d8b
Show file tree
Hide file tree
Showing 4 changed files with 218 additions and 5 deletions.
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ module.exports = require("./lib/acl.js");
module.exports.redisBackend = require("./lib/redis-backend.js");
module.exports.memoryBackend = require("./lib/memory-backend.js");
module.exports.mongodbBackend = require("./lib/mongodb-backend.js");
module.exports.cassandraBackend = require("./lib/cassandra-backend.js");
191 changes: 191 additions & 0 deletions lib/cassandra-backend.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
/**
Cassandra Backend.
Implementation of the storage backend using Cassandra
Attention: The specified keyspace and table must exist beforehand. The 'create table' query:
CREATE TABLE [keyspace].[columnfamily] (
bucketname varchar,
key varchar,
values set<varchar>,
PRIMARY KEY ((bucketname, key))
)
*/
"use strict";

var contract = require('./contract');
var async = require('async');
var _ = require('lodash');

function CassandraBackend(client, keyspace, columnfamily){
this.client = client;
var keyspaceWithTableName = keyspace + "." + columnfamily;
this.queries = {
clean: "TRUNCATE " + keyspaceWithTableName,
get: "SELECT values FROM " + keyspaceWithTableName + " WHERE bucketname = ? AND key = ?",
union: "SELECT values FROM " + keyspaceWithTableName + " WHERE bucketname = ? AND key IN ?",
add: "UPDATE " + keyspaceWithTableName + " SET values = values + ? WHERE bucketname = ? AND key = ?",
del: "DELETE FROM " + keyspaceWithTableName + " WHERE bucketname = ? AND key IN ?",
remove: "UPDATE " + keyspaceWithTableName + " SET values = values - ? WHERE bucketname = ? AND key = ?"
};
}

CassandraBackend.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();
async.series(transaction,function(err){
cb(err instanceof Error? err : undefined);
});
},

/**
Cleans the whole storage.
*/
clean : function(cb){
contract(arguments).params('function').end();
this.client.execute(this.queries.clean, [], cb);
},

/**
Gets the contents at the bucket's key.
*/
get: function(bucket, key, cb) {
contract(arguments)
.params('string', 'string|number', 'function')
.end();
key = encodeText(key);
this.client.execute(this.queries.get, [bucket, key], {hints: ['varchar', 'varchar']}, function(err, result) {
if (err) return cb(err);
if (result.rows.length == 0) return cb(undefined, []);
result = decodeAll(result.rows[0].values);
cb(undefined, result);
});
},

/**
Returns the union of the values in the given keys.
*/
union: function(bucket, keys, cb) {
contract(arguments)
.params('string', 'array', 'function')
.end();
keys = encodeAll(keys);
this.client.execute(this.queries.union, [bucket, keys], {hints: ['varchar', 'set<varchar>']}, function(err, result) {
if (err) return cb(err);
if (result.rows.length == 0) return cb(undefined, []);
result = result.rows.reduce(function(prev, curr) { return prev.concat(decodeAll(curr.values)) }, []);
cb(undefined, _.union(result));
});
},

/**
Adds values to a given key inside a bucket.
*/
add: function(transaction, bucket, key, values) {
contract(arguments)
.params('array', 'string', 'string|number', 'string|array|number')
.end();

if (key == "key") throw new Error("Key name 'key' is not allowed.");
key = encodeText(key);
var self = this;
transaction.push(function (cb) {
values = makeArray(values);
self.client.execute(self.queries.add, [values, bucket, key], {hints: ['set<varchar>', 'varchar', 'varchar']}, function(err) {
if (err) 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();
keys = makeArray(keys);
var self = this;
transaction.push(function (cb) {
self.client.execute(self.queries.del, [bucket, keys], {hints: ['varchar', 'set<varchar>']}, function(err) {
if (err) 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|number', 'string|array|number')
.end();
key = encodeText(key);
var self = this;
values = makeArray(values);
transaction.push(function (cb) {
self.client.execute(self.queries.remove, [values, bucket, key], {hints: ['set<varchar>', 'varchar', 'varchar']}, function(err) {
if (err) return cb(err);
cb(undefined);
});
});
}
};

function encodeText(text) {
if (typeof text == 'number' || text instanceof Number) text = text.toString();
if (typeof text == 'string' || text instanceof String) {
text = encodeURIComponent(text);
text = text.replace(/\./, '%2E');
}
return text;
}

function decodeText(text) {
if (typeof text == 'string' || text instanceof String) {
text = decodeURIComponent(text);
}
return text;
}

function encodeAll(arr) {
if (Array.isArray(arr)) {
var ret = [];
arr.forEach(function(aval) {
ret.push(encodeText(aval));
});
return ret;
} else {
return arr;
}
}

function decodeAll(arr) {
if (Array.isArray(arr)) {
var ret = [];
arr.forEach(function(aval) {
ret.push(decodeText(aval));
});
return ret;
} else {
return arr;
}
}

function makeArray(arr){
return Array.isArray(arr) ? encodeAll(arr) : [encodeText(arr)];
}

exports = module.exports = CassandraBackend;
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"dependencies": {
"async": "~0.9.0",
"bluebird": "^2.3.11",
"cassandra-driver": "^2.0.1",
"lodash": "~2.4.1",
"mongodb": "^1.4.30",
"redis": ">=0.12.1"
Expand Down
30 changes: 25 additions & 5 deletions test/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,26 @@ describe('MongoDB - useSingle', function () {
run()
});

// Attention: keyspace and columnfamily must exist beforehand
describe('Cassandra', function () {
before(function (done) {
var self = this
, cassandra = require('cassandra-driver');

client = new cassandra.Client({contactPoints: ['127.0.0.1']});
client.connect(function(err) {
if (err) return done(err);
client.execute("TRUNCATE acltest.acl", [], function(err) {
if (err) return done(err);
self.backend = new Acl.cassandraBackend(client, "acltest", "acl");
done()
});
});
});

run()
});

describe('Redis', function () {
before(function (done) {
var self = this
Expand All @@ -43,22 +63,22 @@ describe('Redis', function () {
password: null
}
, Redis = require('redis')


var redis = Redis.createClient(options.port, options.host, {no_ready_check: true} )

function start(){
self.backend = new Acl.redisBackend(redis)
done()
}

if (options.password) {
redis.auth(options.password, start)
} else {
start()
}
})

run()
})

Expand All @@ -68,7 +88,7 @@ describe('Memory', function () {
var self = this
self.backend = new Acl.memoryBackend()
})

run()
})

Expand Down

0 comments on commit 9481d8b

Please sign in to comment.