You've already forked node-redis
mirror of
https://github.com/redis/node-redis.git
synced 2025-08-04 15:02:09 +03:00
Fix saving buffers with charsets other than utf-8 while using multi
This will also improve pipelinening for buffers and fixes the return value of Batch.exec Fixes #913
This commit is contained in:
@@ -1,597 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var config = require("../lib/config");
|
||||
var helper = require('../helper');
|
||||
var redis = config.redis;
|
||||
var uuid = require('uuid');
|
||||
|
||||
describe("The 'multi' method", function () {
|
||||
|
||||
helper.allTests(function(parser, ip, args) {
|
||||
|
||||
describe("using " + parser + " and " + ip, function () {
|
||||
var key, value;
|
||||
|
||||
beforeEach(function () {
|
||||
key = uuid.v4();
|
||||
value = uuid.v4();
|
||||
});
|
||||
|
||||
describe("when not connected", function () {
|
||||
var client;
|
||||
|
||||
beforeEach(function (done) {
|
||||
client = redis.createClient.apply(redis.createClient, args);
|
||||
client.once("ready", function () {
|
||||
client.quit();
|
||||
});
|
||||
client.on('end', function () {
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it("reports an error", function (done) {
|
||||
var multi = client.multi();
|
||||
var notBuffering = multi.exec(function (err, res) {
|
||||
assert(err.message.match(/The connection has already been closed/));
|
||||
done();
|
||||
});
|
||||
assert.strictEqual(notBuffering, false);
|
||||
});
|
||||
|
||||
it("reports an error if promisified", function () {
|
||||
return client.multi().execAsync().catch(function(err) {
|
||||
assert(err.message.match(/The connection has already been closed/));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when connected", function () {
|
||||
var client;
|
||||
|
||||
beforeEach(function (done) {
|
||||
client = redis.createClient.apply(redis.createClient, args);
|
||||
client.once("connect", done);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
client.end();
|
||||
});
|
||||
|
||||
it("executes a pipelined multi properly in combination with the offline queue", function (done) {
|
||||
var multi1 = client.multi();
|
||||
multi1.set("m1", "123");
|
||||
multi1.get('m1');
|
||||
multi1.exec(done);
|
||||
});
|
||||
|
||||
it("executes a pipelined multi properly after a reconnect in combination with the offline queue", function (done) {
|
||||
client.once('ready', function () {
|
||||
client.stream.destroy();
|
||||
var called = false;
|
||||
var multi1 = client.multi();
|
||||
multi1.set("m1", "123");
|
||||
multi1.get('m1');
|
||||
multi1.exec(function (err, res) {
|
||||
assert(!err);
|
||||
called = true;
|
||||
});
|
||||
client.once('ready', function () {
|
||||
var multi1 = client.multi();
|
||||
multi1.set("m2", "456");
|
||||
multi1.get('m2');
|
||||
multi1.exec(function (err, res) {
|
||||
assert(called);
|
||||
assert(!err);
|
||||
assert.strictEqual(res[1], '456');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when connection is broken", function () {
|
||||
var client;
|
||||
|
||||
afterEach(function () {
|
||||
client.end();
|
||||
});
|
||||
|
||||
it("return an error even if connection is in broken mode if callback is present", function (done) {
|
||||
client = redis.createClient({
|
||||
host: 'somewhere',
|
||||
port: 6379,
|
||||
max_attempts: 1
|
||||
});
|
||||
|
||||
client.on('error', function(err) {
|
||||
if (/Redis connection in broken state/.test(err.message)) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
client.multi([['set', 'foo', 'bar'], ['get', 'foo']]).exec(function (err, res) {
|
||||
assert(/Redis connection in broken state/.test(err.message));
|
||||
assert.strictEqual(err.errors.length, 0);
|
||||
});
|
||||
});
|
||||
|
||||
it("does not emit an error twice if connection is in broken mode with no callback", function (done) {
|
||||
client = redis.createClient({
|
||||
host: 'somewhere',
|
||||
port: 6379,
|
||||
max_attempts: 1
|
||||
});
|
||||
|
||||
client.on('error', function(err) {
|
||||
// Results in multiple done calls if test fails
|
||||
if (/Redis connection in broken state/.test(err.message)) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
client.multi([['set', 'foo', 'bar'], ['get', 'foo']]).exec();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when ready", function () {
|
||||
var client;
|
||||
|
||||
beforeEach(function (done) {
|
||||
client = redis.createClient.apply(redis.createClient, args);
|
||||
client.once("ready", function () {
|
||||
client.flushdb(function (err) {
|
||||
return done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
client.end();
|
||||
});
|
||||
|
||||
it("returns an empty result array", function (done) {
|
||||
var multi = client.multi();
|
||||
var notBuffering = multi.exec(function (err, res) {
|
||||
assert.strictEqual(err, null);
|
||||
assert.strictEqual(res.length, 0);
|
||||
done();
|
||||
});
|
||||
assert.strictEqual(notBuffering, true);
|
||||
});
|
||||
|
||||
it("runs normal calls in-between multis", function (done) {
|
||||
var multi1 = client.multi();
|
||||
multi1.set("m1", "123");
|
||||
client.set('m2', '456', done);
|
||||
});
|
||||
|
||||
it("runs simultaneous multis with the same client", function (done) {
|
||||
var end = helper.callFuncAfter(done, 2);
|
||||
|
||||
var multi1 = client.multi();
|
||||
multi1.set("m1", "123");
|
||||
multi1.get('m1');
|
||||
|
||||
var multi2 = client.multi();
|
||||
multi2.set("m2", "456");
|
||||
multi2.get('m2');
|
||||
|
||||
multi1.exec(end);
|
||||
multi2.exec(function(err, res) {
|
||||
assert.strictEqual(res[1], '456');
|
||||
end();
|
||||
});
|
||||
});
|
||||
|
||||
it("runs simultaneous multis with the same client version 2", function (done) {
|
||||
var end = helper.callFuncAfter(done, 2);
|
||||
var multi2 = client.multi();
|
||||
var multi1 = client.multi();
|
||||
|
||||
multi2.set("m2", "456");
|
||||
multi1.set("m1", "123");
|
||||
multi1.get('m1');
|
||||
multi2.get('m2');
|
||||
|
||||
multi1.exec(end);
|
||||
multi2.exec(function(err, res) {
|
||||
assert.strictEqual(res[1], '456');
|
||||
end();
|
||||
});
|
||||
});
|
||||
|
||||
it('roles back a transaction when one command in a sequence of commands fails', function (done) {
|
||||
var multi1, multi2;
|
||||
var expected = helper.serverVersionAtLeast(client, [2, 6, 5]) ? helper.isError() : function () {};
|
||||
|
||||
// Provoke an error at queue time
|
||||
multi1 = client.MULTI();
|
||||
multi1.mset("multifoo", "10", "multibar", "20", helper.isString("OK"));
|
||||
|
||||
multi1.set("foo2", expected);
|
||||
multi1.incr("multifoo");
|
||||
multi1.incr("multibar");
|
||||
multi1.exec(function () {
|
||||
// Redis 2.6.5+ will abort transactions with errors
|
||||
// see: http://redis.io/topics/transactions
|
||||
var multibar_expected = 22;
|
||||
var multifoo_expected = 12;
|
||||
if (helper.serverVersionAtLeast(client, [2, 6, 5])) {
|
||||
multibar_expected = 1;
|
||||
multifoo_expected = 1;
|
||||
}
|
||||
|
||||
// Confirm that the previous command, while containing an error, still worked.
|
||||
multi2 = client.multi();
|
||||
multi2.incr("multibar", helper.isNumber(multibar_expected));
|
||||
multi2.incr("multifoo", helper.isNumber(multifoo_expected));
|
||||
multi2.exec(function (err, replies) {
|
||||
assert.strictEqual(multibar_expected, replies[0]);
|
||||
assert.strictEqual(multifoo_expected, replies[1]);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('roles back a transaction when one command in an array of commands fails', function (done) {
|
||||
var expected = helper.serverVersionAtLeast(client, [2, 6, 5]) ? helper.isError() : function () {};
|
||||
|
||||
// test nested multi-bulk replies
|
||||
client.multi([
|
||||
["mget", "multifoo", "multibar", function (err, res) {
|
||||
assert.strictEqual(2, res.length);
|
||||
assert.strictEqual(0, +res[0]);
|
||||
assert.strictEqual(0, +res[1]);
|
||||
}],
|
||||
["set", "foo2", expected],
|
||||
["incr", "multifoo"],
|
||||
["incr", "multibar"]
|
||||
]).exec(function (err, replies) {
|
||||
if (helper.serverVersionAtLeast(client, [2, 6, 5])) {
|
||||
assert.notEqual(err, null);
|
||||
assert.equal(replies, undefined);
|
||||
} else {
|
||||
assert.strictEqual(2, replies[0].length);
|
||||
assert.strictEqual(null, replies[0][0]);
|
||||
assert.strictEqual(null, replies[0][1]);
|
||||
|
||||
assert.strictEqual("1", replies[1].toString());
|
||||
assert.strictEqual("1", replies[2].toString());
|
||||
}
|
||||
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles multiple operations being applied to a set', function (done) {
|
||||
client.sadd("some set", "mem 1");
|
||||
client.sadd(["some set", "mem 2"]);
|
||||
client.sadd("some set", "mem 3");
|
||||
client.sadd("some set", "mem 4");
|
||||
|
||||
// make sure empty mb reply works
|
||||
client.del("some missing set");
|
||||
client.smembers("some missing set", function (err, reply) {
|
||||
// make sure empty mb reply works
|
||||
assert.strictEqual(0, reply.length);
|
||||
});
|
||||
|
||||
// test nested multi-bulk replies with empty mb elements.
|
||||
client.multi([
|
||||
["smembers", ["some set"]],
|
||||
["del", "some set"],
|
||||
["smembers", "some set"]
|
||||
])
|
||||
.scard("some set")
|
||||
.exec(function (err, replies) {
|
||||
assert.strictEqual(4, replies[0].length);
|
||||
assert.strictEqual(0, replies[2].length);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it('allows multiple operations to be performed using constructor with all kinds of syntax', function (done) {
|
||||
var now = Date.now();
|
||||
var arr = ["multihmset", "multibar", "multibaz"];
|
||||
var arr2 = ['some manner of key', 'otherTypes'];
|
||||
var arr3 = [5768, "multibarx", "multifoox"];
|
||||
var arr4 = ["mset", [578, "multibar"], helper.isString('OK')];
|
||||
client.multi([
|
||||
arr4,
|
||||
[["mset", "multifoo2", "multibar2", "multifoo3", "multibar3"], helper.isString('OK')],
|
||||
["hmset", arr],
|
||||
[["hmset", "multihmset2", "multibar2", "multifoo3", "multibar3", "test", helper.isString('OK')]],
|
||||
["hmset", ["multihmset", "multibar", "multifoo", helper.isString('OK')]],
|
||||
["hmset", arr3, helper.isString('OK')],
|
||||
['hmset', now, {123456789: "abcdefghij", "some manner of key": "a type of value", "otherTypes": 555}],
|
||||
['hmset', 'key2', {"0123456789": "abcdefghij", "some manner of key": "a type of value", "otherTypes": 999}, helper.isString('OK')],
|
||||
["HMSET", "multihmset", ["multibar", "multibaz"]],
|
||||
["hmset", "multihmset", ["multibar", "multibaz"], helper.isString('OK')],
|
||||
])
|
||||
.hmget(now, 123456789, 'otherTypes')
|
||||
.hmget('key2', arr2, function noop() {})
|
||||
.hmget(['multihmset2', 'some manner of key', 'multibar3'])
|
||||
.mget('multifoo2', ['multifoo3', 'multifoo'], function(err, res) {
|
||||
assert(res[0], 'multifoo3');
|
||||
assert(res[1], 'multifoo');
|
||||
})
|
||||
.exec(function (err, replies) {
|
||||
assert.equal(arr.length, 3);
|
||||
assert.equal(arr2.length, 2);
|
||||
assert.equal(arr3.length, 3);
|
||||
assert.equal(arr4.length, 3);
|
||||
assert.strictEqual(null, err);
|
||||
assert.equal(replies[10][1], '555');
|
||||
assert.equal(replies[11][0], 'a type of value');
|
||||
assert.strictEqual(replies[12][0], null);
|
||||
assert.equal(replies[12][1], 'test');
|
||||
assert.equal(replies[13][0], 'multibar2');
|
||||
assert.equal(replies[13].length, 3);
|
||||
assert.equal(replies.length, 14);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it('converts a non string key to a string', function(done) {
|
||||
// TODO: Converting the key might change soon again.
|
||||
client.multi().hmset(true, {
|
||||
test: 123,
|
||||
bar: 'baz'
|
||||
}).exec(done);
|
||||
});
|
||||
|
||||
it('runs a multi without any further commands', function(done) {
|
||||
var buffering = client.multi().exec(function(err, res) {
|
||||
assert.strictEqual(err, null);
|
||||
assert.strictEqual(res.length, 0);
|
||||
done();
|
||||
});
|
||||
assert(typeof buffering === 'boolean');
|
||||
});
|
||||
|
||||
it('allows multiple operations to be performed using a chaining API', function (done) {
|
||||
client.multi()
|
||||
.mset('some', '10', 'keys', '20')
|
||||
.incr('some')
|
||||
.incr('keys')
|
||||
.mget('some', ['keys'])
|
||||
.exec(function (err, replies) {
|
||||
assert.strictEqual(null, err);
|
||||
assert.equal('OK', replies[0]);
|
||||
assert.equal(11, replies[1]);
|
||||
assert.equal(21, replies[2]);
|
||||
assert.equal(11, replies[3][0].toString());
|
||||
assert.equal(21, replies[3][1].toString());
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it('allows multiple commands to work the same as normal to be performed using a chaining API', function (done) {
|
||||
client.multi()
|
||||
.mset(['some', '10', 'keys', '20'])
|
||||
.incr(['some', helper.isNumber(11)])
|
||||
.incr(['keys'], helper.isNumber(21))
|
||||
.mget('some', 'keys')
|
||||
.exec(function (err, replies) {
|
||||
assert.strictEqual(null, err);
|
||||
assert.equal('OK', replies[0]);
|
||||
assert.equal(11, replies[1]);
|
||||
assert.equal(21, replies[2]);
|
||||
assert.equal(11, replies[3][0].toString());
|
||||
assert.equal(21, replies[3][1].toString());
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it('allows multiple commands to work the same as normal to be performed using a chaining API promisified', function () {
|
||||
return client.multi()
|
||||
.mset(['some', '10', 'keys', '20'])
|
||||
.incr(['some', helper.isNumber(11)])
|
||||
.incr(['keys'], helper.isNumber(21))
|
||||
.mget('some', 'keys')
|
||||
.execAsync()
|
||||
.then(function (replies) {
|
||||
assert.equal('OK', replies[0]);
|
||||
assert.equal(11, replies[1]);
|
||||
assert.equal(21, replies[2]);
|
||||
assert.equal(11, replies[3][0].toString());
|
||||
assert.equal(21, replies[3][1].toString());
|
||||
});
|
||||
});
|
||||
|
||||
it('allows an array to be provided indicating multiple operations to perform', function (done) {
|
||||
// test nested multi-bulk replies with nulls.
|
||||
client.multi([
|
||||
["mget", ["multifoo", "some", "random value", "keys"]],
|
||||
["incr", "multifoo"]
|
||||
])
|
||||
.exec(function (err, replies) {
|
||||
assert.strictEqual(replies.length, 2);
|
||||
assert.strictEqual(replies[0].length, 4);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it('allows multiple operations to be performed on a hash', function (done) {
|
||||
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[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);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it('reports EXECABORT exceptions when they occur (while queueing)', function (done) {
|
||||
client.multi().config("bar").set("foo").set("bar").exec(function (err, reply) {
|
||||
assert.equal(err.code, "EXECABORT");
|
||||
assert.equal(reply, undefined, "The reply should have been discarded");
|
||||
assert(err.message.match(/^EXECABORT/), "Error message should begin with EXECABORT");
|
||||
assert.equal(err.errors.length, 2, "err.errors should have 2 items");
|
||||
assert.strictEqual(err.errors[0].command, 'SET');
|
||||
assert.strictEqual(err.errors[0].code, 'ERR');
|
||||
assert.strictEqual(err.errors[0].position, 1);
|
||||
assert(/^ERR/.test(err.errors[0].message), "Actuall error message should begin with ERR");
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it('reports multiple exceptions when they occur (while EXEC is running)', function (done) {
|
||||
client.multi().config("bar").debug("foo").eval("return {err='this is an error'}", 0).exec(function (err, reply) {
|
||||
assert.strictEqual(reply.length, 3);
|
||||
assert.equal(reply[0].code, 'ERR');
|
||||
assert.equal(reply[0].command, 'CONFIG');
|
||||
assert.equal(reply[2].code, undefined);
|
||||
assert.equal(reply[2].command, 'EVAL');
|
||||
assert(/^this is an error/.test(reply[2].message));
|
||||
assert(/^ERR/.test(reply[0].message), "Error message should begin with ERR");
|
||||
assert(/^ERR/.test(reply[1].message), "Error message should begin with ERR");
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it('reports multiple exceptions when they occur (while EXEC is running) promisified', function () {
|
||||
return client.multi().config("bar").debug("foo").eval("return {err='this is an error'}", 0).execAsync().then(function (reply) {
|
||||
assert.strictEqual(reply.length, 3);
|
||||
assert.equal(reply[0].code, 'ERR');
|
||||
assert.equal(reply[0].command, 'CONFIG');
|
||||
assert.equal(reply[2].code, undefined);
|
||||
assert.equal(reply[2].command, 'EVAL');
|
||||
assert(/^this is an error/.test(reply[2].message));
|
||||
assert(/^ERR/.test(reply[0].message), "Error message should begin with ERR");
|
||||
assert(/^ERR/.test(reply[1].message), "Error message should begin with ERR");
|
||||
});
|
||||
});
|
||||
|
||||
it('reports multiple exceptions when they occur (while EXEC is running) and calls cb', function (done) {
|
||||
var multi = client.multi();
|
||||
multi.config("bar", helper.isError());
|
||||
multi.set('foo', 'bar', helper.isString('OK'));
|
||||
multi.debug("foo").exec(function (err, reply) {
|
||||
assert.strictEqual(reply.length, 3);
|
||||
assert.strictEqual(reply[0].code, 'ERR');
|
||||
assert(/^ERR/.test(reply[0].message), "Error message should begin with ERR");
|
||||
assert(/^ERR/.test(reply[2].message), "Error message should begin with ERR");
|
||||
assert.strictEqual(reply[1], "OK");
|
||||
client.get('foo', helper.isString('bar', done));
|
||||
});
|
||||
});
|
||||
|
||||
it("emits an error if no callback has been provided and execabort error occured", function (done) {
|
||||
helper.serverVersionAtLeast.call(this, client, [2, 6, 5]);
|
||||
|
||||
var multi = client.multi();
|
||||
multi.config("bar");
|
||||
multi.set("foo");
|
||||
multi.exec();
|
||||
|
||||
client.on('error', function(err) {
|
||||
assert.equal(err.code, "EXECABORT");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should work without any callback", function (done) {
|
||||
helper.serverVersionAtLeast.call(this, client, [2, 6, 5]);
|
||||
|
||||
var multi = client.multi();
|
||||
multi.set("baz", "binary");
|
||||
multi.set("foo", "bar");
|
||||
multi.exec();
|
||||
|
||||
client.get('foo', helper.isString('bar', done));
|
||||
});
|
||||
|
||||
it("should not use a transaction with exec_atomic if only no command is used", function () {
|
||||
var multi = client.multi();
|
||||
var test = false;
|
||||
multi.exec_batch = function () {
|
||||
test = true;
|
||||
};
|
||||
multi.exec_atomic();
|
||||
assert(test);
|
||||
});
|
||||
|
||||
it("should not use a transaction with exec_atomic if only one command is used", function () {
|
||||
var multi = client.multi();
|
||||
var test = false;
|
||||
multi.exec_batch = function () {
|
||||
test = true;
|
||||
};
|
||||
multi.set("baz", "binary");
|
||||
multi.exec_atomic();
|
||||
assert(test);
|
||||
});
|
||||
|
||||
it("should use transaction with exec_atomic and more than one command used", function (done) {
|
||||
helper.serverVersionAtLeast.call(this, client, [2, 6, 5]);
|
||||
|
||||
var multi = client.multi();
|
||||
var test = false;
|
||||
multi.exec_batch = function () {
|
||||
test = true;
|
||||
};
|
||||
multi.set("baz", "binary");
|
||||
multi.get('baz');
|
||||
multi.exec_atomic(done);
|
||||
assert(!test);
|
||||
});
|
||||
|
||||
it("do not mutate arguments in the multi constructor", function (done) {
|
||||
helper.serverVersionAtLeast.call(this, client, [2, 6, 5]);
|
||||
|
||||
var input = [['set', 'foo', 'bar'], ['get', 'foo']];
|
||||
client.multi(input).exec(function (err, res) {
|
||||
assert.strictEqual(input.length, 2);
|
||||
assert.strictEqual(input[0].length, 3);
|
||||
assert.strictEqual(input[1].length, 2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("works properly after a reconnect. issue #897", function (done) {
|
||||
helper.serverVersionAtLeast.call(this, client, [2, 6, 5]);
|
||||
|
||||
client.stream.destroy();
|
||||
client.on('error', function (err) {
|
||||
assert.strictEqual(err.code, 'ECONNREFUSED');
|
||||
});
|
||||
client.on('ready', function () {
|
||||
client.multi([['set', 'foo', 'bar'], ['get', 'foo']]).exec(function (err, res) {
|
||||
assert(!err);
|
||||
assert.strictEqual(res[1], 'bar');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("emits error once if reconnecting after multi has been executed but not yet returned without callback", function (done) {
|
||||
helper.serverVersionAtLeast.call(this, client, [2, 6, 5]);
|
||||
|
||||
client.on('error', function(err) {
|
||||
assert.strictEqual(err.code, 'UNCERTAIN_STATE');
|
||||
done();
|
||||
});
|
||||
|
||||
client.multi().set("foo", 'bar').get('foo').exec();
|
||||
// Abort connection before the value returned
|
||||
client.stream.destroy();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user