Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Some bug fixes:

* An important bug fix in reconnection logic.  Previously, reply callbacks would be invoked twice after
  a reconnect.
* Changed error callback argument to be an actual Error object.

New feature:

* Add friendly syntax for HMSET using an object.
  • Loading branch information...
commit ccce845cc28a436a031706470a09bb428533cc9f 1 parent aa3cefe
@mranney authored
Showing with 172 additions and 33 deletions.
  1. +39 −1 README.md
  2. +12 −0 changelog.md
  3. +64 −21 index.js
  4. +1 −1  package.json
  5. +56 −10 test.js
View
40 README.md
@@ -53,7 +53,7 @@ The performance of `node_redis` improves dramatically with pipelining.
## Usage
-Simple example, included as `example.js`:
+Simple example, included as `examples/simple.js`:
var redis = require("redis"),
client = redis.createClient();
@@ -195,6 +195,44 @@ want to do this:
`client.end()` is useful for timeout cases where something is stuck or taking too long and you want
to start over.
+## Friendlier hash commands
+
+Most Redis commands take a single String or an Array of Strings as arguments, and replies are sent back as a single String or an Array of Strings. When dealing with hash values, there are a couple of useful exceptions to this.
+
+### client.hgetall(hash)
+
+The reply from an HGETALL command will be converted into a JavaScript Object by `node_redis`. That way you can interact
+with the responses using JavaScript syntax.
+
+Example:
+
+ client.hmset("hosts", "mjr", "1", "another", "23", "home", "1234");
+ client.hgetall("hosts", function (err, obj) {
+ console.dir(obj);
+ });
+
+Output:
+
+ { mjr: '1', another: '23', home: '1234' }
+
+### client.hmset(hash, obj, [callback])
+
+Multiple values in a hash can be set by supplying an object:
+
+ client.HMSET(key2, {
+ "0123456789": "abcdefghij",
+ "some manner of key": "a type of value"
+ });
+
+The properties and values of this Object will be set as keys and values in the Redis hash.
+
+### client.hmset(hash, key1, val1, ... keyn, valn, [callback])
+
+Multiple values may also be set by supplying a list:
+
+ client.HMSET(key1, "0123456789", "abcdefghij", "some manner of key", "a type of value");
+
+
## Publish / Subscribe
Here is a simple example of the API for publish / subscribe. This program opens two
View
12 changelog.md
@@ -1,6 +1,18 @@
Changelog
=========
+## v0.5.0 - December 29, 2010
+
+Some bug fixes:
+
+* An important bug fix in reconnection logic. Previously, reply callbacks would be invoked twice after
+ a reconnect.
+* Changed error callback argument to be an actual Error object.
+
+New feature:
+
+* Add friendly syntax for HMSET using an object.
+
## v0.4.1 - December 8, 2010
Remove warning about missing hiredis. You probably do want it though.
View
85 index.js
@@ -77,6 +77,18 @@ function RedisClient(stream, options) {
return_buffers: self.options.return_buffers || false
});
+ // "reply error" is an error sent back by redis
+ self.reply_parser.on("reply error", function (reply) {
+ self.return_error(new Error(reply));
+ });
+ self.reply_parser.on("reply", function (reply) {
+ self.return_reply(reply);
+ });
+ // "error" is bad. Somehow the parser got confused. It'll try to reset and continue.
+ self.reply_parser.on("error", function (err) {
+ self.emit("error", new Error("Redis reply parser error: " + err.stack));
+ });
+
this.stream.on("connect", function () {
if (exports.debug_mode) {
console.log("Stream connected");
@@ -86,18 +98,6 @@ function RedisClient(stream, options) {
self.command_queue = new Queue();
self.emitted_end = false;
- // "reply error" is an error sent back by redis
- self.reply_parser.on("reply error", function (reply) {
- self.return_error(reply);
- });
- self.reply_parser.on("reply", function (reply) {
- self.return_reply(reply);
- });
- // "error" is bad. Somehow the parser got confused. It'll try to reset and continue.
- self.reply_parser.on("error", function (err) {
- self.emit("error", new Error("Redis reply parser error: " + err.stack));
- });
-
self.retry_timer = null;
self.retry_delay = 250;
self.stream.setNoDelay();
@@ -196,7 +196,10 @@ RedisClient.prototype.connection_gone = function (why) {
console.log("Retry conneciton in " + self.retry_delay + " ms");
}
self.attempts += 1;
- self.emit("reconnecting", "delay " + self.retry_delay + ", attempt " + self.attempts);
+ self.emit("reconnecting", {
+ delay: self.retry_delay,
+ attempt: self.attempts
+ });
self.retry_timer = setTimeout(function () {
if (exports.debug_mode) {
console.log("Retrying connection...");
@@ -233,16 +236,16 @@ RedisClient.prototype.return_error = function (err) {
if (command_obj && typeof command_obj.callback === "function") {
try {
command_obj.callback(err);
- } catch (err) {
+ } catch (callback_err) {
// if a callback throws an exception, re-throw it on a new stack so the parser can keep going
process.nextTick(function () {
- throw err;
+ throw callback_err;
});
}
} else {
- console.log("no callback to send error: " + util.inspect(err));
+ console.log("node_redis: no callback to send error: " + util.inspect(err));
// this will probably not make it anywhere useful, but we might as well throw
- throw new Error(err);
+ throw err;
}
};
@@ -439,9 +442,8 @@ function Multi(client, args) {
}
}
-
-// Official source is: http://code.google.com/p/redis/wiki/CommandReference
-// This list is taken from src/redis.c
+// Official source is: http://redis.io/commands.json
+// This list needs to be updated, and perhaps auto-updated somehow.
[
// string commands
"get", "set", "setnx", "setex", "append", "substr", "strlen", "del", "exists", "incr", "decr", "mget",
@@ -453,7 +455,7 @@ function Multi(client, args) {
"zadd", "zincrby", "zrem", "zremrangebyscore", "zremrangebyrank", "zunionstore", "zinterstore", "zrange", "zrangebyscore", "zrevrangebyscore",
"zcount", "zrevrange", "zcard", "zscore", "zrank", "zrevrank",
// hash commands
- "hset", "hsetnx", "hget", "hmset", "hmget", "hincrby", "hdel", "hlen", "hkeys", "hgetall", "hexists", "incrby", "decrby",
+ "hset", "hsetnx", "hget", "hmget", "hincrby", "hdel", "hlen", "hkeys", "hgetall", "hexists", "incrby", "decrby",
// misc
"getset", "mset", "msetnx", "randomkey", "select", "move", "rename", "renamenx", "expire", "expireat", "keys", "dbsize", "auth", "ping", "echo",
"save", "bgsave", "bgwriteaof", "shutdown", "lastsave", "type", "sync", "flushdb", "flushall", "sort", "info",
@@ -476,6 +478,47 @@ function Multi(client, args) {
Multi.prototype[command.toUpperCase()] = Multi.prototype[command];
});
+RedisClient.prototype.hmset = function () {
+ var args = to_array(arguments), tmp_args;
+ if (args.length >= 2 && typeof args[0] === "string" && typeof args[1] === "object") {
+ tmp_args = [ "hmset", args[0] ];
+ Object.keys(args[1]).map(function (key) {
+ tmp_args.push(key);
+ tmp_args.push(args[1][key]);
+ });
+ if (args[2]) {
+ tmp_args.push(args[2]);
+ }
+ args = tmp_args;
+ } else {
+ args.unshift("hmset");
+ }
+
+ this.send_command.apply(this, args);
+};
+RedisClient.prototype.HMSET = RedisClient.prototype.hmset;
+
+Multi.prototype.hmset = function () {
+ var args = to_array(arguments), tmp_args;
+ if (args.length >= 2 && typeof args[0] === "string" && typeof args[1] === "object") {
+ tmp_args = [ "hmset", args[0] ];
+ Object.keys(args[1]).map(function (key) {
+ tmp_args.push(key);
+ tmp_args.push(args[1][key]);
+ });
+ if (args[2]) {
+ tmp_args.push(args[2]);
+ }
+ args = tmp_args;
+ } else {
+ args.unshift("hmset");
+ }
+
+ this.queue.push(args);
+ return this;
+};
+Multi.prototype.HMSET = Multi.prototype.hmset;
+
Multi.prototype.exec = function (callback) {
var self = this;
View
2  package.json
@@ -1,5 +1,5 @@
{ "name" : "redis",
- "version" : "0.4.2",
+ "version" : "0.5.0",
"description" : "Redis client library",
"author": "Matt Ranney <mjr@ranney.com>",
"contributors": [
View
66 test.js
@@ -211,13 +211,19 @@ tests.MULTI_6 = function () {
client.multi()
.hmset("multihash", "a", "foo", "b", 1)
+ .hmset("multihash", {
+ extra: "fancy",
+ things: "here"
+ })
.hgetall("multihash")
.exec(function (err, replies) {
assert.strictEqual(null, err);
assert.equal("OK", replies[0]);
- assert.equal(Object.keys(replies[1]).length, 2);
- assert.equal("foo", replies[1].a.toString());
- assert.equal("1", replies[1].b.toString());
+ assert.equal(Object.keys(replies[2]).length, 4);
+ assert.equal("foo", replies[2].a);
+ assert.equal("1", replies[2].b);
+ assert.equal("fancy", replies[2].extra);
+ assert.equal("here", replies[2].things);
next(name);
});
};
@@ -237,6 +243,30 @@ tests.WATCH_MULTI = function () {
}
};
+tests.reconnect = function () {
+ var name = "reconnect";
+
+ client.set("recon 1", "one");
+ client.set("recon 2", "two", function (err, res) {
+ // Do not do this in normal programs. This is to simulate the server closing on us.
+ // For orderly shutdown in normal programs, do client.quit()
+ client.stream.destroy();
+ });
+
+ client.on("reconnecting", function on_recon(params) {
+ client.on("connect", function on_connect() {
+ client.select(test_db_num, require_string("OK", name));
+ client.get("recon 1", require_string("one", name));
+ client.get("recon 1", require_string("one", name));
+ client.get("recon 2", require_string("two", name));
+ client.get("recon 2", require_string("two", name));
+ client.removeListener("connect", on_connect);
+ client.removeListener("reconnecting", on_recon);
+ next(name);
+ });
+ });
+};
+
tests.HSET = function () {
var key = "test hash",
field1 = new Buffer("0123456789"),
@@ -257,17 +287,30 @@ tests.HSET = function () {
client.HSET(key, field2, value2, last(name, require_number(0, name)));
};
+
tests.HMGET = function () {
- var key = "test hash", name = "HMGET";
+ var key1 = "test hash 1", key2 = "test hash 2", name = "HMGET";
+
+ // redis-like hmset syntax
+ client.HMSET(key1, "0123456789", "abcdefghij", "some manner of key", "a type of value", require_string("OK", name));
- client.HMSET(key, "0123456789", "abcdefghij", "some manner of key", "a type of value", require_string("OK", name));
+ // fancy hmset syntax
+ client.HMSET(key2, {
+ "0123456789": "abcdefghij",
+ "some manner of key": "a type of value"
+ }, require_string("OK", name));
- client.HMGET(key, "0123456789", "some manner of key", function (err, reply) {
+ client.HMGET(key1, "0123456789", "some manner of key", function (err, reply) {
+ assert.strictEqual("abcdefghij", reply[0].toString(), name);
+ assert.strictEqual("a type of value", reply[1].toString(), name);
+ });
+
+ client.HMGET(key2, "0123456789", "some manner of key", function (err, reply) {
assert.strictEqual("abcdefghij", reply[0].toString(), name);
assert.strictEqual("a type of value", reply[1].toString(), name);
});
- client.HMGET(key, "missing thing", "another missing thing", function (err, reply) {
+ client.HMGET(key1, "missing thing", "another missing thing", function (err, reply) {
assert.strictEqual(null, reply[0], name);
assert.strictEqual(null, reply[1], name);
next(name);
@@ -1014,7 +1057,10 @@ function run_next_test() {
console.log("Using reply parser " + client.reply_parser.name);
-client.on("connect", function () {
+client.on("connect", function start_tests() {
+ // remove listener so we don't restart all tests on reconnect
+ client.removeListener("connect", start_tests);
+
// Fetch and stash info results in case anybody needs info on the server we are using.
client.info(function (err, reply) {
var obj = {};
@@ -1055,8 +1101,8 @@ client3.on("error", function (err) {
process.exit();
});
-client.on("reconnecting", function (msg) {
- console.log("reconnecting: " + msg);
+client.on("reconnecting", function (params) {
+ console.log("reconnecting: " + util.inspect(params));
});
process.on('uncaughtException', function (err) {
Please sign in to comment.
Something went wrong with that request. Please try again.