|
|
@ -31,88 +31,101 @@ RedisClient.prototype.batch = RedisClient.prototype.BATCH = function batch (args |
|
|
|
return new Multi(this, args); |
|
|
|
}; |
|
|
|
|
|
|
|
// Store db in this.select_db to restore it on reconnect
|
|
|
|
RedisClient.prototype.select = RedisClient.prototype.SELECT = function select (db, callback) { |
|
|
|
var self = this; |
|
|
|
return this.internal_send_command('select', [db], function (err, res) { |
|
|
|
function select_callback (self, db, callback) { |
|
|
|
return function (err, res) { |
|
|
|
if (err === null) { |
|
|
|
// Store db in this.select_db to restore it on reconnect
|
|
|
|
self.selected_db = db; |
|
|
|
} |
|
|
|
utils.callback_or_emit(self, callback, err, res); |
|
|
|
}); |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
RedisClient.prototype.monitor = RedisClient.prototype.MONITOR = function (callback) { |
|
|
|
// Use a individual command, as this is a special case that does not has to be checked for any other command
|
|
|
|
var self = this; |
|
|
|
return this.internal_send_command('monitor', [], function (err, res) { |
|
|
|
RedisClient.prototype.select = RedisClient.prototype.SELECT = function select (db, callback) { |
|
|
|
return this.internal_send_command('select', [db], select_callback(this, db, callback)); |
|
|
|
}; |
|
|
|
|
|
|
|
Multi.prototype.select = Multi.prototype.SELECT = function select (db, callback) { |
|
|
|
this.queue.push(['select', [db], select_callback(this._client, db, callback)]); |
|
|
|
return this; |
|
|
|
}; |
|
|
|
|
|
|
|
function monitor_callback (self, callback) { |
|
|
|
return function (err, res) { |
|
|
|
if (err === null) { |
|
|
|
self.reply_parser.returnReply = function (reply) { |
|
|
|
// If in monitor mode, all normal commands are still working and we only want to emit the streamlined commands
|
|
|
|
// As this is not the average use case and monitor is expensive anyway, let's change the code here, to improve
|
|
|
|
// the average performance of all other commands in case of no monitor mode
|
|
|
|
if (self.monitoring) { |
|
|
|
var replyStr; |
|
|
|
if (self.buffers && Buffer.isBuffer(reply)) { |
|
|
|
replyStr = reply.toString(); |
|
|
|
} else { |
|
|
|
replyStr = reply; |
|
|
|
} |
|
|
|
// While reconnecting the redis server does not recognize the client as in monitor mode anymore
|
|
|
|
// Therefor the monitor command has to finish before it catches further commands
|
|
|
|
if (typeof replyStr === 'string' && utils.monitor_regex.test(replyStr)) { |
|
|
|
var timestamp = replyStr.slice(0, replyStr.indexOf(' ')); |
|
|
|
var args = replyStr.slice(replyStr.indexOf('"') + 1, -1).split('" "').map(function (elem) { |
|
|
|
return elem.replace(/\\"/g, '"'); |
|
|
|
}); |
|
|
|
self.emit('monitor', timestamp, args, replyStr); |
|
|
|
return; |
|
|
|
self.monitoring = true; |
|
|
|
} |
|
|
|
utils.callback_or_emit(self, callback, err, res); |
|
|
|
}; |
|
|
|
} |
|
|
|
self.return_reply(reply); |
|
|
|
|
|
|
|
RedisClient.prototype.monitor = RedisClient.prototype.MONITOR = function monitor (callback) { |
|
|
|
// Use a individual command, as this is a special case that does not has to be checked for any other command
|
|
|
|
return this.internal_send_command('monitor', [], monitor_callback(this, callback)); |
|
|
|
}; |
|
|
|
self.monitoring = true; |
|
|
|
|
|
|
|
// Only works with batch, not in a transaction
|
|
|
|
Multi.prototype.monitor = Multi.prototype.MONITOR = function monitor (callback) { |
|
|
|
// Use a individual command, as this is a special case that does not has to be checked for any other command
|
|
|
|
if (this.exec !== this.exec_transaction) { |
|
|
|
this.queue.push(['monitor', [], monitor_callback(this._client, callback)]); |
|
|
|
return this; |
|
|
|
} |
|
|
|
utils.callback_or_emit(self, callback, err, res); |
|
|
|
}); |
|
|
|
var err = new Error( |
|
|
|
'You used the monitor command in combination with a transaction. Due to faulty return values of ' + |
|
|
|
'Redis in this context, the monitor command is now executed without transaction instead and ignored ' + |
|
|
|
'in the multi statement.' |
|
|
|
); |
|
|
|
err.command = 'MONITOR'; |
|
|
|
utils.reply_in_order(this._client, callback, err); |
|
|
|
this._client.monitor('monitor', callback); |
|
|
|
return this; |
|
|
|
}; |
|
|
|
|
|
|
|
RedisClient.prototype.quit = RedisClient.prototype.QUIT = function (callback) { |
|
|
|
var self = this; |
|
|
|
var callback_hook = function (err, res) { |
|
|
|
// TODO: Improve this by handling everything with coherend error codes and find out if there's anything missing
|
|
|
|
if (err && (err.code === 'NR_OFFLINE' || |
|
|
|
err.message === 'Redis connection gone from close event.' || |
|
|
|
err.message === 'The command can\'t be processed. The connection has already been closed.' |
|
|
|
)) { |
|
|
|
function quit_callback (self, callback) { |
|
|
|
return function (err, res) { |
|
|
|
if (err && err.code === 'NR_OFFLINE') { |
|
|
|
// Pretent the quit command worked properly in this case.
|
|
|
|
// Either the quit landed in the offline queue and was flushed at the reconnect
|
|
|
|
// or the offline queue is deactivated and the command was rejected right away
|
|
|
|
// or the stream is not writable
|
|
|
|
// or while sending the quit, the connection dropped
|
|
|
|
// or while sending the quit, the connection ended / closed
|
|
|
|
err = null; |
|
|
|
res = 'OK'; |
|
|
|
} |
|
|
|
utils.callback_or_emit(self, callback, err, res); |
|
|
|
if (self.stream.writable) { |
|
|
|
// If the socket is still alive, kill it. This could happen if quit got a NR_OFFLINE error code
|
|
|
|
self.stream.destroy(); |
|
|
|
} |
|
|
|
}; |
|
|
|
var backpressure_indicator = this.internal_send_command('quit', [], callback_hook); |
|
|
|
} |
|
|
|
|
|
|
|
RedisClient.prototype.QUIT = RedisClient.prototype.quit = function (callback) { |
|
|
|
// TODO: Consider this for v.3
|
|
|
|
// Allow the quit command to be fired as soon as possible to prevent it landing in the offline queue.
|
|
|
|
// this.ready = this.offline_queue.length === 0;
|
|
|
|
var backpressure_indicator = this.internal_send_command('quit', [], quit_callback(this, callback)); |
|
|
|
// Calling quit should always end the connection, no matter if there's a connection or not
|
|
|
|
this.closing = true; |
|
|
|
this.ready = false; |
|
|
|
return backpressure_indicator; |
|
|
|
}; |
|
|
|
|
|
|
|
// Store info in this.server_info after each call
|
|
|
|
RedisClient.prototype.info = RedisClient.prototype.INFO = function info (section, callback) { |
|
|
|
var self = this; |
|
|
|
var ready = this.ready; |
|
|
|
var args = []; |
|
|
|
if (typeof section === 'function') { |
|
|
|
callback = section; |
|
|
|
} else if (section !== undefined) { |
|
|
|
args = Array.isArray(section) ? section : [section]; |
|
|
|
} |
|
|
|
this.ready = ready || this.offline_queue.length === 0; // keep the execution order intakt
|
|
|
|
var tmp = this.internal_send_command('info', args, function (err, res) { |
|
|
|
// Only works with batch, not in a transaction
|
|
|
|
Multi.prototype.QUIT = Multi.prototype.quit = function (callback) { |
|
|
|
var self = this._client; |
|
|
|
var call_on_write = function () { |
|
|
|
// If called in a multi context, we expect redis is available
|
|
|
|
self.closing = true; |
|
|
|
self.ready = false; |
|
|
|
}; |
|
|
|
this.queue.push(['quit', [], quit_callback(self, callback), call_on_write]); |
|
|
|
return this; |
|
|
|
}; |
|
|
|
|
|
|
|
function info_callback (self, callback) { |
|
|
|
return function (err, res) { |
|
|
|
if (res) { |
|
|
|
var obj = {}; |
|
|
|
var lines = res.toString().split('\r\n'); |
|
|
@ -146,20 +159,33 @@ RedisClient.prototype.info = RedisClient.prototype.INFO = function info (section |
|
|
|
self.server_info = {}; |
|
|
|
} |
|
|
|
utils.callback_or_emit(self, callback, err, res); |
|
|
|
}); |
|
|
|
this.ready = ready; |
|
|
|
return tmp; |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
RedisClient.prototype.auth = RedisClient.prototype.AUTH = function auth (pass, callback) { |
|
|
|
var self = this; |
|
|
|
var ready = this.ready; |
|
|
|
debug('Sending auth to ' + self.address + ' id ' + self.connection_id); |
|
|
|
// Store info in this.server_info after each call
|
|
|
|
RedisClient.prototype.info = RedisClient.prototype.INFO = function info (section, callback) { |
|
|
|
var args = []; |
|
|
|
if (typeof section === 'function') { |
|
|
|
callback = section; |
|
|
|
} else if (section !== undefined) { |
|
|
|
args = Array.isArray(section) ? section : [section]; |
|
|
|
} |
|
|
|
return this.internal_send_command('info', args, info_callback(this, callback)); |
|
|
|
}; |
|
|
|
|
|
|
|
// Stash auth for connect and reconnect.
|
|
|
|
this.auth_pass = pass; |
|
|
|
this.ready = ready || this.offline_queue.length === 0; // keep the execution order intakt
|
|
|
|
var tmp = this.internal_send_command('auth', [pass], function (err, res) { |
|
|
|
Multi.prototype.info = Multi.prototype.INFO = function info (section, callback) { |
|
|
|
var args = []; |
|
|
|
if (typeof section === 'function') { |
|
|
|
callback = section; |
|
|
|
} else if (section !== undefined) { |
|
|
|
args = Array.isArray(section) ? section : [section]; |
|
|
|
} |
|
|
|
this.queue.push(['info', args, info_callback(this._client, callback)]); |
|
|
|
return this; |
|
|
|
}; |
|
|
|
|
|
|
|
function auth_callback (self, pass, callback) { |
|
|
|
return function (err, res) { |
|
|
|
if (err) { |
|
|
|
if (no_password_is_set.test(err.message)) { |
|
|
|
self.warn('Warning: Redis server does not require a password, but a password was supplied.'); |
|
|
@ -175,11 +201,31 @@ RedisClient.prototype.auth = RedisClient.prototype.AUTH = function auth (pass, c |
|
|
|
} |
|
|
|
} |
|
|
|
utils.callback_or_emit(self, callback, err, res); |
|
|
|
}); |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
RedisClient.prototype.auth = RedisClient.prototype.AUTH = function auth (pass, callback) { |
|
|
|
debug('Sending auth to ' + this.address + ' id ' + this.connection_id); |
|
|
|
|
|
|
|
// Stash auth for connect and reconnect.
|
|
|
|
this.auth_pass = pass; |
|
|
|
var ready = this.ready; |
|
|
|
this.ready = ready || this.offline_queue.length === 0; |
|
|
|
var tmp = this.internal_send_command('auth', [pass], auth_callback(this, pass, callback)); |
|
|
|
this.ready = ready; |
|
|
|
return tmp; |
|
|
|
}; |
|
|
|
|
|
|
|
// Only works with batch, not in a transaction
|
|
|
|
Multi.prototype.auth = Multi.prototype.AUTH = function auth (pass, callback) { |
|
|
|
debug('Sending auth to ' + this.address + ' id ' + this.connection_id); |
|
|
|
|
|
|
|
// Stash auth for connect and reconnect.
|
|
|
|
this.auth_pass = pass; |
|
|
|
this.queue.push(['auth', [pass], auth_callback(this._client, callback)]); |
|
|
|
return this; |
|
|
|
}; |
|
|
|
|
|
|
|
RedisClient.prototype.hmset = RedisClient.prototype.HMSET = function hmset () { |
|
|
|
var arr, |
|
|
|
len = arguments.length, |
|
|
@ -198,7 +244,7 @@ RedisClient.prototype.hmset = RedisClient.prototype.HMSET = function hmset () { |
|
|
|
for (; i < len; i += 1) { |
|
|
|
arr[i + 1] = arguments[1][i]; |
|
|
|
} |
|
|
|
} else if (typeof arguments[1] === 'object' && (arguments.length === 2 || arguments.length === 3 && typeof arguments[2] === 'function' || typeof arguments[2] === 'undefined')) { |
|
|
|
} else if (typeof arguments[1] === 'object' && (arguments.length === 2 || arguments.length === 3 && (typeof arguments[2] === 'function' || typeof arguments[2] === 'undefined'))) { |
|
|
|
arr = [arguments[0]]; |
|
|
|
for (var field in arguments[1]) { // jshint ignore: line
|
|
|
|
arr.push(field, arguments[1][field]); |
|
|
@ -219,6 +265,46 @@ RedisClient.prototype.hmset = RedisClient.prototype.HMSET = function hmset () { |
|
|
|
return this.internal_send_command('hmset', arr, callback); |
|
|
|
}; |
|
|
|
|
|
|
|
Multi.prototype.hmset = Multi.prototype.HMSET = function hmset () { |
|
|
|
var arr, |
|
|
|
len = arguments.length, |
|
|
|
callback, |
|
|
|
i = 0; |
|
|
|
if (Array.isArray(arguments[0])) { |
|
|
|
arr = arguments[0]; |
|
|
|
callback = arguments[1]; |
|
|
|
} else if (Array.isArray(arguments[1])) { |
|
|
|
if (len === 3) { |
|
|
|
callback = arguments[2]; |
|
|
|
} |
|
|
|
len = arguments[1].length; |
|
|
|
arr = new Array(len + 1); |
|
|
|
arr[0] = arguments[0]; |
|
|
|
for (; i < len; i += 1) { |
|
|
|
arr[i + 1] = arguments[1][i]; |
|
|
|
} |
|
|
|
} else if (typeof arguments[1] === 'object' && (arguments.length === 2 || arguments.length === 3 && (typeof arguments[2] === 'function' || typeof arguments[2] === 'undefined'))) { |
|
|
|
arr = [arguments[0]]; |
|
|
|
for (var field in arguments[1]) { // jshint ignore: line
|
|
|
|
arr.push(field, arguments[1][field]); |
|
|
|
} |
|
|
|
callback = arguments[2]; |
|
|
|
} else { |
|
|
|
len = arguments.length; |
|
|
|
// The later should not be the average use case
|
|
|
|
if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) { |
|
|
|
len--; |
|
|
|
callback = arguments[len]; |
|
|
|
} |
|
|
|
arr = new Array(len); |
|
|
|
for (; i < len; i += 1) { |
|
|
|
arr[i] = arguments[i]; |
|
|
|
} |
|
|
|
} |
|
|
|
this.queue.push(['hmset', arr, callback]); |
|
|
|
return this; |
|
|
|
}; |
|
|
|
|
|
|
|
RedisClient.prototype.subscribe = RedisClient.prototype.SUBSCRIBE = function subscribe () { |
|
|
|
var arr, |
|
|
|
len = arguments.length, |
|
|
@ -378,7 +464,7 @@ Multi.prototype.psubscribe = Multi.prototype.PSUBSCRIBE = function psubscribe () |
|
|
|
arr[i] = arguments[i]; |
|
|
|
} |
|
|
|
} |
|
|
|
var self = this; |
|
|
|
var self = this._client; |
|
|
|
var call_on_write = function () { |
|
|
|
self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1; |
|
|
|
}; |
|
|
@ -434,7 +520,7 @@ Multi.prototype.punsubscribe = Multi.prototype.PUNSUBSCRIBE = function punsubscr |
|
|
|
arr[i] = arguments[i]; |
|
|
|
} |
|
|
|
} |
|
|
|
var self = this; |
|
|
|
var self = this._client; |
|
|
|
var call_on_write = function () { |
|
|
|
// Pub sub has to be activated even if not in pub sub mode, as the return value is manipulated in the callback
|
|
|
|
self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1; |
|
|
|