Skip to content

Commit aab1fc8

Browse files
committed
Merge pull request #838 from fintura/consistent-commands
Consistent commands arguments. All commands are from now on behaving the same no matter if they are on multi or no and they all take an array as either the first or second argument. Fixes #686 #369 #422 #390 and Closes #634
2 parents e24f056 + a0c92b0 commit aab1fc8

23 files changed

+332
-87
lines changed

.jshintrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
"node": true, // Enable globals available when code is running inside of the NodeJS runtime environment.
1717
"mocha": true,
1818

19+
// Relaxing options
20+
"boss": true, // Accept things like `while (command = keys.shift()) { ... }`
21+
1922
"overrides": {
2023
"examples/*.js": {
2124
"unused": false

index.js

Lines changed: 72 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -863,8 +863,16 @@ RedisClient.prototype.end = function () {
863863
function Multi(client, args) {
864864
this._client = client;
865865
this.queue = [["multi"]];
866+
var command, tmp_args;
866867
if (Array.isArray(args)) {
867-
this.queue = this.queue.concat(args);
868+
while (tmp_args = args.shift()) {
869+
command = tmp_args.shift();
870+
if (Array.isArray(command)) {
871+
this[command[0]].apply(this, command.slice(1).concat(tmp_args));
872+
} else {
873+
this[command].apply(this, tmp_args);
874+
}
875+
}
868876
}
869877
}
870878

@@ -878,16 +886,32 @@ commands.forEach(function (fullCommand) {
878886
return;
879887
}
880888

881-
RedisClient.prototype[command] = function (args, callback) {
882-
if (Array.isArray(args)) {
883-
return this.send_command(command, args, callback);
889+
RedisClient.prototype[command] = function (key, arg, callback) {
890+
if (Array.isArray(key)) {
891+
return this.send_command(command, key, arg);
892+
}
893+
if (Array.isArray(arg)) {
894+
arg.unshift(key);
895+
return this.send_command(command, arg, callback);
884896
}
885897
return this.send_command(command, to_array(arguments));
886898
};
887899
RedisClient.prototype[command.toUpperCase()] = RedisClient.prototype[command];
888900

889-
Multi.prototype[command] = function () {
890-
this.queue.push([command].concat(to_array(arguments)));
901+
Multi.prototype[command] = function (key, arg, callback) {
902+
if (Array.isArray(key)) {
903+
if (arg) {
904+
key.push(arg);
905+
}
906+
this.queue.push([command].concat(key));
907+
} else if (Array.isArray(arg)) {
908+
if (callback) {
909+
arg.push(callback);
910+
}
911+
this.queue.push([command, key].concat(arg));
912+
} else {
913+
this.queue.push([command].concat(to_array(arguments)));
914+
}
891915
return this;
892916
};
893917
Multi.prototype[command.toUpperCase()] = Multi.prototype[command];
@@ -919,70 +943,63 @@ RedisClient.prototype.auth = RedisClient.prototype.AUTH = function (pass, callba
919943
}
920944
};
921945

922-
RedisClient.prototype.hmget = RedisClient.prototype.HMGET = function (arg1, arg2, arg3) {
923-
if (Array.isArray(arg2) && typeof arg3 === "function") {
924-
return this.send_command("hmget", [arg1].concat(arg2), arg3);
925-
} else if (Array.isArray(arg1) && typeof arg2 === "function") {
926-
return this.send_command("hmget", arg1, arg2);
927-
} else {
928-
return this.send_command("hmget", to_array(arguments));
946+
RedisClient.prototype.hmset = RedisClient.prototype.HMSET = function (key, args, callback) {
947+
var field, tmp_args;
948+
if (Array.isArray(key)) {
949+
return this.send_command("hmset", key, args);
929950
}
930-
};
931-
932-
RedisClient.prototype.hmset = RedisClient.prototype.HMSET = function (args, callback) {
933-
var tmp_args, tmp_keys, i, il, key;
934-
935951
if (Array.isArray(args)) {
936-
return this.send_command("hmset", args, callback);
937-
}
938-
939-
args = to_array(arguments);
940-
if (typeof args[args.length - 1] === "function") {
941-
callback = args[args.length - 1];
942-
args.length -= 1;
943-
} else {
944-
callback = null;
952+
return this.send_command("hmset", [key].concat(args), callback);
945953
}
946-
947-
if (args.length === 2 && (typeof args[0] === "string" || typeof args[0] === "number") && typeof args[1] === "object") {
954+
if (typeof args === "object") {
948955
// User does: client.hmset(key, {key1: val1, key2: val2})
949956
// assuming key is a string, i.e. email address
950957

951958
// if key is a number, i.e. timestamp, convert to string
952-
if (typeof args[0] === "number") {
953-
args[0] = args[0].toString();
959+
// TODO: This seems random and no other command get's the key converted => either all or none should behave like this
960+
if (typeof key !== "string") {
961+
key = key.toString();
954962
}
955-
956-
tmp_args = [ args[0] ];
957-
tmp_keys = Object.keys(args[1]);
958-
for (i = 0, il = tmp_keys.length; i < il ; i++) {
959-
key = tmp_keys[i];
960-
tmp_args.push(key);
961-
tmp_args.push(args[1][key]);
963+
tmp_args = [key];
964+
var fields = Object.keys(args);
965+
while (field = fields.shift()) {
966+
tmp_args.push(field, args[field]);
962967
}
963-
args = tmp_args;
968+
return this.send_command("hmset", tmp_args, callback);
964969
}
965-
966-
return this.send_command("hmset", args, callback);
970+
return this.send_command("hmset", to_array(arguments));
967971
};
968972

969-
Multi.prototype.hmset = Multi.prototype.HMSET = function () {
970-
var args = to_array(arguments), tmp_args;
971-
if (args.length >= 2 && typeof args[0] === "string" && typeof args[1] === "object") {
972-
tmp_args = [ "hmset", args[0] ];
973-
Object.keys(args[1]).map(function (key) {
974-
tmp_args.push(key);
975-
tmp_args.push(args[1][key]);
976-
});
977-
if (args[2]) {
978-
tmp_args.push(args[2]);
973+
Multi.prototype.hmset = Multi.prototype.HMSET = function (key, args, callback) {
974+
var tmp_args, field;
975+
if (Array.isArray(key)) {
976+
if (args) {
977+
key.push(args);
978+
}
979+
tmp_args = ['hmset'].concat(key);
980+
} else if (Array.isArray(args)) {
981+
if (callback) {
982+
args.push(callback);
983+
}
984+
tmp_args = ['hmset', key].concat(args);
985+
} else if (typeof args === "object") {
986+
tmp_args = ["hmset", key];
987+
if (typeof key !== "string") {
988+
key = key.toString();
989+
}
990+
var fields = Object.keys(args);
991+
while (field = fields.shift()) {
992+
tmp_args.push(field);
993+
tmp_args.push(args[field]);
994+
}
995+
if (callback) {
996+
tmp_args.push(callback);
979997
}
980-
args = tmp_args;
981998
} else {
982-
args.unshift("hmset");
999+
tmp_args = to_array(arguments);
1000+
tmp_args.unshift("hmset");
9831001
}
984-
985-
this.queue.push(args);
1002+
this.queue.push(tmp_args);
9861003
return this;
9871004
};
9881005

test/commands/blpop.spec.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@ describe("The 'blpop' method", function () {
3333
});
3434
});
3535

36+
it('pops value immediately if list contains values using array notation', function (done) {
37+
bclient = redis.createClient.apply(redis.createClient, args);
38+
client.rpush(["blocking list", "initial value"], helper.isNumber(1));
39+
bclient.blpop(["blocking list", 0], function (err, value) {
40+
assert.strictEqual(value[0], "blocking list");
41+
assert.strictEqual(value[1], "initial value");
42+
return done(err);
43+
});
44+
});
45+
3646
it('waits for value if list is not yet populated', function (done) {
3747
bclient = redis.createClient.apply(redis.createClient, args);
3848
bclient.blpop("blocking list 2", 5, function (err, value) {

test/commands/client.spec.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,17 @@ describe("The 'client' method", function () {
3737
});
3838
});
3939

40+
it("lists connected clients when invoked with array syntax on client", function (done) {
41+
client.multi().client(["list"]).exec(function(err, results) {
42+
assert(pattern.test(results[0]), "expected string '" + results + "' to match " + pattern.toString());
43+
return done();
44+
});
45+
});
46+
4047
it("lists connected clients when invoked with multi's array syntax", function (done) {
41-
client.multi().client("list").exec(function(err, results) {
48+
client.multi([
49+
['client', 'list']
50+
]).exec(function(err, results) {
4251
assert(pattern.test(results[0]), "expected string '" + results + "' to match " + pattern.toString());
4352
return done();
4453
});

test/commands/dbsize.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ describe("The 'dbsize' method", function () {
7171
var oldSize;
7272

7373
beforeEach(function (done) {
74-
client.dbsize([], function (err, res) {
74+
client.dbsize(function (err, res) {
7575
helper.isType.number()(err, res);
7676
assert.strictEqual(res, 0, "Initial db size should be 0");
7777

test/commands/del.spec.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,20 @@ describe("The 'del' method", function () {
3636
client.get('apple', helper.isNull(done));
3737
});
3838

39+
it('allows multiple keys to be deleted with the array syntax', function (done) {
40+
client.mset('foo', 'bar', 'apple', 'banana');
41+
client.del(['foo', 'apple'], helper.isNumber(2));
42+
client.get('foo', helper.isNull());
43+
client.get('apple', helper.isNull(done));
44+
});
45+
46+
it('allows multiple keys to be deleted with the array syntax and no callback', function (done) {
47+
client.mset('foo', 'bar', 'apple', 'banana');
48+
client.del(['foo', 'apple']);
49+
client.get('foo', helper.isNull());
50+
client.get('apple', helper.isNull(done));
51+
});
52+
3953
afterEach(function () {
4054
client.end();
4155
});

test/commands/exits.spec.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ describe("The 'exits' method", function () {
2424
client.EXISTS('foo', helper.isNumber(1, done));
2525
});
2626

27+
it('returns 1 if the key exists with array syntax', function (done) {
28+
client.set('foo', 'bar');
29+
client.EXISTS(['foo'], helper.isNumber(1, done));
30+
});
31+
2732
it('returns 0 if the key does not exist', function (done) {
2833
client.exists('bar', helper.isNumber(0, done));
2934
});

test/commands/expire.spec.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ describe("The 'expire' method", function () {
2020
});
2121

2222
it('expires key after timeout', function (done) {
23+
client.set(['expiry key', 'bar'], helper.isString("OK"));
24+
client.EXPIRE("expiry key", "1", helper.isNumber(1));
25+
setTimeout(function () {
26+
client.exists(["expiry key"], helper.isNumber(0, done));
27+
}, 1100);
28+
});
29+
30+
it('expires key after timeout with array syntax', function (done) {
2331
client.set(['expiry key', 'bar'], helper.isString("OK"));
2432
client.EXPIRE(["expiry key", "1"], helper.isNumber(1));
2533
setTimeout(function () {

test/commands/flushdb.spec.js

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -58,36 +58,50 @@ describe("The 'flushdb' method", function () {
5858
describe("when there is data in Redis", function () {
5959

6060
beforeEach(function (done) {
61-
var end = helper.callFuncAfter(function () {
62-
client.flushdb(helper.isString("OK", done));
63-
}, 2);
64-
client.mset(key, uuid.v4(), key2, uuid.v4(), helper.isNotError(end));
61+
client.mset(key, uuid.v4(), key2, uuid.v4(), helper.isNotError());
6562
client.dbsize([], function (err, res) {
6663
helper.isType.positiveNumber()(err, res);
6764
assert.equal(res, 2, 'Two keys should have been inserted');
68-
end();
65+
done();
6966
});
7067
});
7168

7269
it("deletes all the keys", function (done) {
73-
client.mget(key, key2, function (err, res) {
74-
assert.strictEqual(null, err, "Unexpected error returned");
75-
assert.strictEqual(true, Array.isArray(res), "Results object should be an array.");
76-
assert.strictEqual(2, res.length, "Results array should have length 2.");
77-
assert.strictEqual(null, res[0], "Redis key should have been flushed.");
78-
assert.strictEqual(null, res[1], "Redis key should have been flushed.");
79-
done(err);
70+
client.flushdb(function(err, res) {
71+
assert.equal(res, 'OK');
72+
client.mget(key, key2, function (err, res) {
73+
assert.strictEqual(null, err, "Unexpected error returned");
74+
assert.strictEqual(true, Array.isArray(res), "Results object should be an array.");
75+
assert.strictEqual(2, res.length, "Results array should have length 2.");
76+
assert.strictEqual(null, res[0], "Redis key should have been flushed.");
77+
assert.strictEqual(null, res[1], "Redis key should have been flushed.");
78+
done(err);
79+
});
8080
});
8181
});
8282

8383
it("results in a db size of zero", function (done) {
84-
client.dbsize([], function (err, res) {
85-
helper.isNotError()(err, res);
86-
helper.isType.number()(err, res);
87-
assert.strictEqual(0, res, "Flushing db should result in db size 0");
88-
done();
84+
client.flushdb(function(err, res) {
85+
client.dbsize([], function (err, res) {
86+
helper.isNotError()(err, res);
87+
helper.isType.number()(err, res);
88+
assert.strictEqual(0, res, "Flushing db should result in db size 0");
89+
done();
90+
});
8991
});
9092
});
93+
94+
it("results in a db size of zero without a callback", function (done) {
95+
client.flushdb();
96+
setTimeout(function(err, res) {
97+
client.dbsize([], function (err, res) {
98+
helper.isNotError()(err, res);
99+
helper.isType.number()(err, res);
100+
assert.strictEqual(0, res, "Flushing db should result in db size 0");
101+
done();
102+
});
103+
}, 25);
104+
});
91105
});
92106
});
93107
});

test/commands/get.spec.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,21 @@ describe("The 'get' method", function () {
7070
done(err);
7171
});
7272
});
73+
74+
it("gets the value correctly with array syntax and the callback being in the array", function (done) {
75+
client.GET([key, function (err, res) {
76+
helper.isString(value)(err, res);
77+
done(err);
78+
}]);
79+
});
80+
81+
it("should not throw on a get without callback (even if it's not useful", function (done) {
82+
client.GET(key);
83+
client.on('error', function(err) {
84+
throw err;
85+
});
86+
setTimeout(done, 50);
87+
});
7388
});
7489

7590
describe("when the key does not exist in Redis", function () {

0 commit comments

Comments
 (0)