redis-node
Version:
A Complete Redis Client for Node.js
204 lines (185 loc) • 7.91 kB
JavaScript
/*
© 2010 by Brian Noguchi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
var Reply = require("../reply").Reply,
CR = require("../reply").CR,
LF = require("../reply").LF,
BulkReply = require("./bulkReply").BulkReply,
IntegerReply = require("./integerReply").IntegerReply;
MultibulkReply = exports.MultibulkReply = function (client, context) {
this.client = client;
this.context = context;
this.isComplete = false;
this.replyValue = null;
this.expectedProxy = {replyValue: "", i: 0, isComplete: false};
};
Object.defineProperty(MultibulkReply.prototype, 'latest', {
get: function () {
var ctx = this.context;
return (ctx.scope === "exec") ?
ctx.currCommandName :
ctx.scope;
}
});
MultibulkReply.prototype.parse = function (data, atDataIndex) {
var dataLen = data.length,
childReplies = this.replies,
expected, expectedProxy;
while (atDataIndex < dataLen) {
if (typeof this.expected === "undefined") {
expectedProxy = this.expectedProxy;
atDataIndex = IntegerReply.prototype.parse.call(expectedProxy, data, atDataIndex);
if (expectedProxy.isComplete) {
expected = this.expected = expectedProxy.replyValue;
if (expected === -1) {
this.replyValue = null;
this.isComplete = true;
break;
}
if (expected === 0) { // For '*0\r\n'
this.replyValue = this.isHashValuable[this.latest] ? null : [];
this.isComplete = true;
break;
}
childReplies = this.replies = new Array(expected);
this.numChildren = 0;
}
continue;
}
latestReply = childReplies[this.numChildren-1];
if (latestReply && !latestReply.isComplete) {
atDataIndex = latestReply.parse(data, atDataIndex);
} else {
var newReply = Reply.fromTypeCode( data[atDataIndex++], this.client, this.context );
if (!newReply) {
continue;
}
latestReply = childReplies[this.numChildren++] = newReply;
}
if (latestReply.isComplete) {
if (this.numChildren === 1) {
// Find the child2ValueFn to use to add any child replyValue to this.replyValue.
// We do this by evaluating the first reply in this multibulk. This gives us enough
// clues about what form this.replyValue should take - either a Message, PMessage,
// Hash, or Array.
// Determine if this is a PUBSUB message by peeking at the first child reply
var childReply1Value = latestReply.replyValue,
childReply1ValueLen = childReply1Value && childReply1Value.length;
if (expected === 3 && childReply1ValueLen === 7 && childReply1Value === "message") {
// If this is a PUBSUB message
this.isMessage = true;
this.child2ValueFn = this.child2MessageValue;
this.replyValue = {};
} else if (expected === 4 && childReply1ValueLen === 8 && childReply1Value === "pmessage") {
// If this is a PUBSUB pmessage
this.isPMessage = true;
this.child2ValueFn = this.child2PMessageValue;
this.replyValue = {};
} else {
this.child2ValueFn = this.getTransformerFromContext();
}
}
this.child2ValueFn();
if (this.expected === this.numChildren) {
this.isComplete = true;
break;
}
}
}
return atDataIndex;
};
MultibulkReply.prototype.child2MessageValue = function () {
var childReplies = this.replies,
numChildReplies = this.numChildren,
childReply = childReplies[numChildReplies-1];
if (numChildReplies === 1) {
// Do nothing because the 1st reply is just "message" or "pmessage"
} else if (numChildReplies === 2) {
this.replyValue.channelName = childReply.replyValue;
} else if (numChildReplies === 3) { // === expected
this.replyValue.message = childReply.replyValue;
} else {
throw new Error("Out of bounds unexpected.");
}
};
MultibulkReply.prototype.child2PMessageValue = function () {
var childReplies = this.replies,
numChildReplies = this.numChildren,
childReply = childReplies[numChildReplies-1];
if (numChildReplies === 1) {
// Do nothing because the 1st reply is just "message" or "pmessage"
} else if (numChildReplies === 2) {
this.replyValue.channelPattern = childReply.replyValue;
} else if (numChildReplies === 3) { // === expected
this.replyValue.channelName = childReply.replyValue;
} else if (numChildReplies === 4) {
this.replyValue.message = childReply.replyValue;
} else {
throw new Error("Out of bounds unexpected.");
}
};
MultibulkReply.prototype.child2HashValue = function () {
var childReplies = this.replies,
numChildReplies = this.numChildren,
latestChildReply = childReplies[numChildReplies-1];
if (numChildReplies % 2 === 1) {
this.nextKey = latestChildReply.replyValue;
} else {
this.replyValue[this.nextKey] = latestChildReply.replyValue;
}
};
MultibulkReply.prototype.child2ArrayValue = function () {
var childReplies = this.replies,
numChildReplies = this.numChildren,
latestChildReply = childReplies[numChildReplies-1];
this.replyValue.push(latestChildReply.replyValue);
};
// Multibulks can be found with:
// -Transactions => return an array of replies
// -Sort => return an array where elements are hashes or values
// -Hgetall => return a hash
// -Mget => return an array
// -Others => return an array of values
// Most extreme case is a transaction of sorts
MultibulkReply.prototype.getTransformerFromContext = function () {
var context = this.context,
latest = this.latest,
ret;
if (latest === "sort") {
if (!context.parsingSort) {
ret = this.child2ArrayValue;
this.replyValue = [];
}
else {
ret = this.child2HashValue;
this.replyValue = {};
}
} else if (this.isHashValuable[latest]) {
ret = this.child2HashValue;
this.replyValue = {};
} else {
// Among other scenarios, this else also takes care of (latest === "exec") or
// when we're in special transaction exiting territory
ret = this.child2ArrayValue;
this.replyValue = [];
}
return ret;
};
MultibulkReply.prototype.isHashValuable = {
hgetall: true
};