Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Refactor & Database module updates

+ Updated mongodb module to 1.2.2
+ Updated mysql module to 2.0.0-alpha5
+ Updated pg module to 0.9.0
+ Added Storage::nativeMulti
+ Added RedisMulti class on storages/

Storages now have a .nativeMulti method, which returns a
multi object that uses the underlying database's transaction
mechanisms. If the underlying database doesn't support
transactions, the regular Multi is used instead.
  • Loading branch information...
commit a1b02b456a8281bfa80682e67028d843ccbbb32d 1 parent f3df0bd
Ernesto Méndez mendezcode authored
6 dependencies.json
View
@@ -1,12 +1,12 @@
{
"mongodb": {
- "mongodb" : "= 1.0.2"
+ "mongodb" : "= 1.2.2"
},
"mysql": {
- "mysql" : "= 0.9.6"
+ "mysql" : "= 2.0.0-alpha5"
},
"postgres": {
- "pg" : "= 0.8.4"
+ "pg" : "= 0.9.0"
},
"redis": {
"redis" : "= 0.7.2",
7 drivers/mongodb.js
View
@@ -36,7 +36,8 @@ function MongoDB(app, config) {
database: 'default',
storage: null,
username: '',
- password: ''
+ password: '',
+ safe: true
}, config || {});
if (typeof config.port != 'number') config.port = parseInt(config.port, 10);
@@ -67,7 +68,7 @@ function MongoDB(app, config) {
}
// Set db
- self.db = new Db(config.database, new Server(config.host, config.port, {}));
+ self.db = new Db(config.database, new Server(config.host, config.port, {}), {safe: config.safe});
// Add async task
app.addReadyTask();
@@ -81,7 +82,7 @@ function MongoDB(app, config) {
// Set client
self.client = client;
-
+
// Set storage
if (typeof config.storage == 'string') {
self.storage = app.getResource('storages/' + config.storage);
2  drivers/mysql.js
View
@@ -45,7 +45,7 @@ function MySQL(app, config) {
this.config = config;
// Set client
- this.client = mysql.createClient(config);
+ this.client = mysql.createConnection(config);
// Assign storage
if (typeof config.storage == 'string') {
12 lib/model.js
View
@@ -262,11 +262,13 @@ Model.prototype.createModel = function(o) {
// Add property descriptors
for (key in o) {
- if (key == 'id') {
- // The id property is immutable
- descriptor[key] = {value: o[key], writable: false, enumerable: true, configurable: false};
- } else {
- descriptor[key] = {value: o[key], writable: true, enumerable: true, configurable: true};
+ if (o.hasOwnProperty(key)) {
+ if (key == 'id') {
+ // The id property is immutable
+ descriptor[key] = {value: o[key], writable: false, enumerable: true, configurable: false};
+ } else {
+ descriptor[key] = {value: o[key], writable: true, enumerable: true, configurable: true};
+ }
}
}
10 lib/storage.js
View
@@ -231,5 +231,15 @@ Storage.prototype.decrBy = function(key, value, callback) {
Storage.prototype.multi = function(config) {
return new Multi(this, config);
}
+
+/**
+ Native multi, uses the storage's internal transaction mechanisms
+
+ If the storage backend doesn't support native multi, storage.multi() is used instead.
+ */
+
+Storage.prototype.nativeMulti = function(config) {
+ return this.multi(config);
+}
module.exports = Storage;
2  middleware/logger/transport-mongodb.js
View
@@ -130,7 +130,7 @@ function initMongo(config, callback) {
var self = this;
// Set db
- self.db = new Db(config.database, new Server(config.host, config.port, {}));
+ self.db = new Db(config.database, new Server(config.host, config.port, {}), {safe: true});
// Get client
self.db.open(function(err, client) {
5 storages/mongodb.js
View
@@ -34,7 +34,8 @@ function MongoStorage(app, config) {
database: 'store',
collection: 'keyvalue',
username: '',
- password: ''
+ password: '',
+ safe: true
}, config);
if (typeof config.port != 'number') config.port = parseInt(config.port, 10);
@@ -82,7 +83,7 @@ function MongoStorage(app, config) {
}
// Set db
- self.db = new Db(config.database, new Server(config.host, config.port, {}));
+ self.db = new Db(config.database, new Server(config.host, config.port, {}), {safe: config.safe});
// Add async task
app.addReadyTask();
89 storages/redis.js
View
@@ -6,7 +6,8 @@
var _ = require('underscore'),
redis = protos.requireDependency('redis', 'Redis Storage'),
- util = require('util');
+ util = require('util'),
+ slice = Array.prototype.slice;
/**
Redis Storage class
@@ -22,9 +23,10 @@ function RedisStorage(app, config) {
var self = this;
- config = config || {};
- config.host = config.host || 'localhost';
- config.port = config.port || 6379;
+ config = protos.extend({
+ host: 'localhost',
+ port: 6379,
+ }, config || {});
/**
Application instance
@@ -103,9 +105,6 @@ function RedisStorage(app, config) {
util.inherits(RedisStorage, protos.lib.storage);
-RedisStorage.prototype.options = {};
-
-
/* Storage API get */
RedisStorage.prototype.get = function(key, callback) {
@@ -322,4 +321,80 @@ RedisStorage.prototype.decrBy = function(key, value, callback) {
});
}
+/* Storage API multi (override) */
+
+RedisStorage.prototype.nativeMulti = function() {
+ return new RedisMulti(this);
+}
+
+////////////// REDIS MULTI
+
+function RedisMulti(instance) {
+
+ // The Native Storage Multi for RedisStorage works by replacing the
+ // internal calls to `this.client` with calls to a multi object. This
+ // queues the calls instead of executing them immediately.
+
+ // Additionally, the exec() method of the multi object has no effect,
+ // in case the storage method uses multi internally, it will just reflect
+ // as an additional statement being queued in the transaction.
+
+ var multi = instance.client.multi();
+
+ this.multi = multi;
+ this.instance = instance;
+
+ // Fake instance, used in place of the original storage instance
+ this.fakeInstance = {client: multi}
+
+ // Add original exec method
+ multi.__exec = multi.exec;
+
+ // Replace original multi exec method with empty function
+ multi.exec = this.fakeExec;
+
+}
+
+// Exec function, gets replaced with fakeExec when queuing statements
+
+RedisMulti.prototype.exec = function(callback) {
+ var self = this;
+ var multi = this.multi;
+ multi.queue = multi.queue.filter(function(arr, i) {
+ var len = arr.length;
+ var valid = (len > 1 || i === 0); // Valid for arrays with more than 1 items (except for the first one)
+ if (valid && arr[len-1] instanceof Function) arr.pop(); // Multi doesn't need callback functions
+ return valid;
+ });
+ multi.__exec(function(err, results) {
+ multi.queue = [[ 'MULTI' ]]; // Reset multi after you're done
+ callback.call(self, err, results);
+ });
+}
+
+// Original exec function, used to perform original execution
+
+RedisMulti.prototype.__exec = RedisMulti.prototype.exec;
+
+// Fake function, does nothing when calling exec
+
+RedisMulti.prototype.fakeExec = function() {
+
+}
+
+// Define RedisMulti interface, based on RedisStorage
+
+Object.keys(RedisStorage.prototype).forEach(function(key) {
+ if (key != 'multi') {
+ var method = RedisStorage.prototype[key];
+ if (method instanceof Function) {
+ RedisMulti.prototype[key] = function() {
+ this.exec = this.fakeExec;
+ this.instance[key].apply(this.fakeInstance, arguments);
+ this.exec = this.__exec;
+ }
+ }
+ }
+});
+
module.exports = RedisStorage;
4 test/drivers/mongodb.js
View
@@ -91,7 +91,7 @@ var batch = vows.describe('drivers/mongodb.js').addBatch({
topic: function() {
var promise = new EventEmitter();
- var db = new Db(config.database, new Server(config.host, config.port), {});
+ var db = new Db(config.database, new Server(config.host, config.port), {safe: true});
db.open(function(err, client) {
if (err) throw err;
@@ -130,7 +130,7 @@ var batch = vows.describe('drivers/mongodb.js').addBatch({
},
'Cleaned up collection': function(results) {
- assert.deepEqual(results, ['OK', []]);
+ assert.deepEqual(results, [0, []]);
}
}
4 test/drivers/mysql.js
View
@@ -6,13 +6,13 @@ var app = require('../fixtures/bootstrap'),
colorize = protos.util.colorize,
ModelBatch = require('../fixtures/model-batch'),
Multi = require('multi'),
- createClient = require('mysql').createClient,
+ createConnection = require('mysql').createConnection,
EventEmitter = require('events').EventEmitter;
var mysql, multi, model, storageMulti, modelBatch;
var config = app.config.drivers.mysql,
- client = createClient(config),
+ client = createConnection(config),
mclient = new Multi(client);
var table = config.table;
112 test/storages/redis-native-multi.js
View
@@ -0,0 +1,112 @@
+
+var app = require('../fixtures/bootstrap'),
+ vows = require('vows'),
+ util = require('util'),
+ assert = require('assert'),
+ EventEmitter = require('events').EventEmitter;
+
+var multi, storage;
+
+/*
+ All of the redis native multi methods work exactly the same
+ that is, they're dynamically generated and rely on the original
+ storage code.
+
+ It will just suffice to test a few methods to know if the interface
+ is working as expected.
+*/
+
+vows.describe('storages/redis-native-multi.js').addBatch({
+
+ 'Preliminaries': {
+
+ topic: function() {
+
+ storage = app.getResource('storages/redis');
+ multi = storage.nativeMulti();
+
+ return storage;
+
+ },
+
+ 'Properly configured storage': function(storage) {
+ assert.isTrue(storage.constructor === protos.storages.redis);
+ }
+
+ }
+
+}).addBatch({
+
+ 'Integrity Tests': {
+
+ topic: function() {
+
+ var promise = new EventEmitter();
+
+ storage.delete(['a', 'b', 'c', 'myhash'], function(err) {
+
+ if (err) {
+
+ throw err;
+
+ } else {
+
+ multi.set('a', 1);
+ multi.set({b: 2, c: 3});
+ multi.setHash('myhash', {one: 1, two: 2});
+ multi.incr('a');
+ multi.decr('c');
+
+ multi.exec(function(err, results) {
+
+ if (err) {
+
+ throw err;
+
+ } else {
+
+ multi.get('a');
+ multi.get(['b', 'c']);
+ multi.getHash('myhash');
+
+ multi.exec(function(err, results) {
+
+ if (err) {
+
+ throw err;
+
+ } else {
+
+ promise.emit('success', results);
+
+ }
+
+ });
+
+ }
+
+ });
+
+ }
+
+ });
+
+ return promise;
+
+ },
+
+ "Uses the storage backend's transaction mechanism (multi)": function(results) {
+ var expected = [ //[ '2', '2', '2', { one: '1', two: '2' } ]
+ '2', // Increased 1 to a (originally 1)
+ '2', // b remains unaffected
+ '2', // Decreased 1 from c (originally 3)
+ {one: '1', two: '2'} // Original hash value
+ ];
+
+ assert.deepEqual(results, expected);
+
+ }
+
+ }
+
+}).export(module);
Please sign in to comment.
Something went wrong with that request. Please try again.