1
0
mirror of https://github.com/redis/node-redis.git synced 2025-08-09 00:22:08 +03:00
Files
node-redis/lib/individualCommands.js

445 lines
17 KiB
JavaScript

'use strict';
var utils = require('./utils');
var debug = require('./debug');
var Multi = require('./multi');
var no_password_is_set = /no password is set/;
var loading = /LOADING/;
var RedisClient = require('../').RedisClient;
/********************************************************************************************
Replace built-in redis functions
The callback may be hooked as needed. The same does not apply to the rest of the function.
State should not be set outside of the callback if not absolutly necessary.
This is important to make sure it works the same as single command or in a multi context.
To make sure everything works with the offline queue use the "call_on_write" function.
This is going to be executed while writing to the stream.
TODO: Implement individal command generation as soon as possible to prevent divergent code
on single and multi calls!
********************************************************************************************/
RedisClient.prototype.multi = RedisClient.prototype.MULTI = function multi (args) {
var multi = new Multi(this, args);
multi.exec = multi.EXEC = multi.exec_transaction;
return multi;
};
// ATTENTION: This is not a native function but is still handled as a individual command as it behaves just the same as multi
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) {
if (err === null) {
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) {
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.return_reply(reply);
};
self.monitoring = true;
}
utils.callback_or_emit(self, callback, err, res);
});
};
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.'
)) {
// 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
err = null;
res = 'OK';
}
utils.callback_or_emit(self, callback, err, res);
};
var backpressure_indicator = this.internal_send_command('quit', [], callback_hook);
// Calling quit should always end the connection, no matter if there's a connection or not
this.closing = true;
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) {
if (res) {
var obj = {};
var lines = res.toString().split('\r\n');
var line, parts, sub_parts;
for (var i = 0; i < lines.length; i++) {
parts = lines[i].split(':');
if (parts[1]) {
if (parts[0].indexOf('db') === 0) {
sub_parts = parts[1].split(',');
obj[parts[0]] = {};
while (line = sub_parts.pop()) {
line = line.split('=');
obj[parts[0]][line[0]] = +line[1];
}
} else {
obj[parts[0]] = parts[1];
}
}
}
obj.versions = [];
/* istanbul ignore else: some redis servers do not send the version */
if (obj.redis_version) {
obj.redis_version.split('.').forEach(function (num) {
obj.versions.push(+num);
});
}
// Expose info key/vals to users
self.server_info = obj;
} else {
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);
// 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) {
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.');
err = null;
res = 'OK';
} else if (loading.test(err.message)) {
// If redis is still loading the db, it will not authenticate and everything else will fail
debug('Redis still loading, trying to authenticate later');
setTimeout(function () {
self.auth(pass, callback);
}, 100);
return;
}
}
utils.callback_or_emit(self, callback, err, res);
});
this.ready = ready;
return tmp;
};
RedisClient.prototype.hmset = RedisClient.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];
}
}
return this.internal_send_command('hmset', arr, callback);
};
RedisClient.prototype.subscribe = RedisClient.prototype.SUBSCRIBE = function subscribe () {
var arr,
len = arguments.length,
callback,
i = 0;
if (Array.isArray(arguments[0])) {
arr = arguments[0];
callback = arguments[1];
} 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];
}
}
var self = this;
var call_on_write = function () {
self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
};
return this.internal_send_command('subscribe', arr, callback, call_on_write);
};
Multi.prototype.subscribe = Multi.prototype.SUBSCRIBE = function subscribe () {
var arr,
len = arguments.length,
callback,
i = 0;
if (Array.isArray(arguments[0])) {
arr = arguments[0];
callback = arguments[1];
} 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];
}
}
var self = this._client;
var call_on_write = function () {
self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
};
this.queue.push(['subscribe', arr, callback, call_on_write]);
return this;
};
RedisClient.prototype.unsubscribe = RedisClient.prototype.UNSUBSCRIBE = function unsubscribe () {
var arr,
len = arguments.length,
callback,
i = 0;
if (Array.isArray(arguments[0])) {
arr = arguments[0];
callback = arguments[1];
} 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];
}
}
var self = this;
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;
};
return this.internal_send_command('unsubscribe', arr, callback, call_on_write);
};
Multi.prototype.unsubscribe = Multi.prototype.UNSUBSCRIBE = function unsubscribe () {
var arr,
len = arguments.length,
callback,
i = 0;
if (Array.isArray(arguments[0])) {
arr = arguments[0];
callback = arguments[1];
} 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];
}
}
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;
};
this.queue.push(['unsubscribe', arr, callback, call_on_write]);
return this;
};
RedisClient.prototype.psubscribe = RedisClient.prototype.PSUBSCRIBE = function psubscribe () {
var arr,
len = arguments.length,
callback,
i = 0;
if (Array.isArray(arguments[0])) {
arr = arguments[0];
callback = arguments[1];
} 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];
}
}
var self = this;
var call_on_write = function () {
self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
};
return this.internal_send_command('psubscribe', arr, callback, call_on_write);
};
Multi.prototype.psubscribe = Multi.prototype.PSUBSCRIBE = function psubscribe () {
var arr,
len = arguments.length,
callback,
i = 0;
if (Array.isArray(arguments[0])) {
arr = arguments[0];
callback = arguments[1];
} 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];
}
}
var self = this;
var call_on_write = function () {
self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
};
this.queue.push(['psubscribe', arr, callback, call_on_write]);
return this;
};
RedisClient.prototype.punsubscribe = RedisClient.prototype.PUNSUBSCRIBE = function punsubscribe () {
var arr,
len = arguments.length,
callback,
i = 0;
if (Array.isArray(arguments[0])) {
arr = arguments[0];
callback = arguments[1];
} 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];
}
}
var self = this;
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;
};
return this.internal_send_command('punsubscribe', arr, callback, call_on_write);
};
Multi.prototype.punsubscribe = Multi.prototype.PUNSUBSCRIBE = function punsubscribe () {
var arr,
len = arguments.length,
callback,
i = 0;
if (Array.isArray(arguments[0])) {
arr = arguments[0];
callback = arguments[1];
} 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];
}
}
var self = this;
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;
};
this.queue.push(['punsubscribe', arr, callback, call_on_write]);
return this;
};