Browse Source

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.
gh-pages v0.5.0
Matt Ranney 14 years ago
parent
commit
ccce845cc2
  1. 40
      README.md
  2. 12
      changelog.md
  3. 85
      index.js
  4. 2
      package.json
  5. 66
      test.js

40
README.md

@ -53,7 +53,7 @@ The performance of `node_redis` improves dramatically with pipelining.
## Usage ## Usage
Simple example, included as `example.js`: Simple example, included as `examples/simple.js`:
var redis = require("redis"), var redis = require("redis"),
client = redis.createClient(); 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 `client.end()` is useful for timeout cases where something is stuck or taking too long and you want
to start over. 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 ## Publish / Subscribe
Here is a simple example of the API for publish / subscribe. This program opens two Here is a simple example of the API for publish / subscribe. This program opens two

12
changelog.md

@ -1,6 +1,18 @@
Changelog 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 ## v0.4.1 - December 8, 2010
Remove warning about missing hiredis. You probably do want it though. Remove warning about missing hiredis. You probably do want it though.

85
index.js

@ -77,6 +77,18 @@ function RedisClient(stream, options) {
return_buffers: self.options.return_buffers || false 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 () { this.stream.on("connect", function () {
if (exports.debug_mode) { if (exports.debug_mode) {
console.log("Stream connected"); console.log("Stream connected");
@ -86,18 +98,6 @@ function RedisClient(stream, options) {
self.command_queue = new Queue(); self.command_queue = new Queue();
self.emitted_end = false; 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_timer = null;
self.retry_delay = 250; self.retry_delay = 250;
self.stream.setNoDelay(); self.stream.setNoDelay();
@ -196,7 +196,10 @@ RedisClient.prototype.connection_gone = function (why) {
console.log("Retry conneciton in " + self.retry_delay + " ms"); console.log("Retry conneciton in " + self.retry_delay + " ms");
} }
self.attempts += 1; 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 () { self.retry_timer = setTimeout(function () {
if (exports.debug_mode) { if (exports.debug_mode) {
console.log("Retrying connection..."); console.log("Retrying connection...");
@ -233,16 +236,16 @@ RedisClient.prototype.return_error = function (err) {
if (command_obj && typeof command_obj.callback === "function") { if (command_obj && typeof command_obj.callback === "function") {
try { try {
command_obj.callback(err); 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 // if a callback throws an exception, re-throw it on a new stack so the parser can keep going
process.nextTick(function () { process.nextTick(function () {
throw err; throw callback_err;
}); });
} }
} else { } 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 // 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://redis.io/commands.json
// Official source is: http://code.google.com/p/redis/wiki/CommandReference // This list needs to be updated, and perhaps auto-updated somehow.
// This list is taken from src/redis.c
[ [
// string commands // string commands
"get", "set", "setnx", "setex", "append", "substr", "strlen", "del", "exists", "incr", "decr", "mget", "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", "zadd", "zincrby", "zrem", "zremrangebyscore", "zremrangebyrank", "zunionstore", "zinterstore", "zrange", "zrangebyscore", "zrevrangebyscore",
"zcount", "zrevrange", "zcard", "zscore", "zrank", "zrevrank", "zcount", "zrevrange", "zcard", "zscore", "zrank", "zrevrank",
// hash commands // 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 // misc
"getset", "mset", "msetnx", "randomkey", "select", "move", "rename", "renamenx", "expire", "expireat", "keys", "dbsize", "auth", "ping", "echo", "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", "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]; 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) { Multi.prototype.exec = function (callback) {
var self = this; var self = this;

2
package.json

@ -1,5 +1,5 @@
{ "name" : "redis", { "name" : "redis",
"version" : "0.4.2", "version" : "0.5.0",
"description" : "Redis client library", "description" : "Redis client library",
"author": "Matt Ranney <mjr@ranney.com>", "author": "Matt Ranney <mjr@ranney.com>",
"contributors": [ "contributors": [

66
test.js

@ -211,13 +211,19 @@ tests.MULTI_6 = function () {
client.multi() client.multi()
.hmset("multihash", "a", "foo", "b", 1) .hmset("multihash", "a", "foo", "b", 1)
.hmset("multihash", {
extra: "fancy",
things: "here"
})
.hgetall("multihash") .hgetall("multihash")
.exec(function (err, replies) { .exec(function (err, replies) {
assert.strictEqual(null, err); assert.strictEqual(null, err);
assert.equal("OK", replies[0]); assert.equal("OK", replies[0]);
assert.equal(Object.keys(replies[1]).length, 2); assert.equal(Object.keys(replies[2]).length, 4);
assert.equal("foo", replies[1].a.toString()); assert.equal("foo", replies[2].a);
assert.equal("1", replies[1].b.toString()); assert.equal("1", replies[2].b);
assert.equal("fancy", replies[2].extra);
assert.equal("here", replies[2].things);
next(name); 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 () { tests.HSET = function () {
var key = "test hash", var key = "test hash",
field1 = new Buffer("0123456789"), field1 = new Buffer("0123456789"),
@ -257,17 +287,30 @@ tests.HSET = function () {
client.HSET(key, field2, value2, last(name, require_number(0, name))); client.HSET(key, field2, value2, last(name, require_number(0, name)));
}; };
tests.HMGET = function () { 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("abcdefghij", reply[0].toString(), name);
assert.strictEqual("a type of value", reply[1].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[0], name);
assert.strictEqual(null, reply[1], name); assert.strictEqual(null, reply[1], name);
next(name); next(name);
@ -1014,7 +1057,10 @@ function run_next_test() {
console.log("Using reply parser " + client.reply_parser.name); 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. // Fetch and stash info results in case anybody needs info on the server we are using.
client.info(function (err, reply) { client.info(function (err, reply) {
var obj = {}; var obj = {};
@ -1055,8 +1101,8 @@ client3.on("error", function (err) {
process.exit(); process.exit();
}); });
client.on("reconnecting", function (msg) { client.on("reconnecting", function (params) {
console.log("reconnecting: " + msg); console.log("reconnecting: " + util.inspect(params));
}); });
process.on('uncaughtException', function (err) { process.on('uncaughtException', function (err) {

Loading…
Cancel
Save