Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Handle nested multi-bulk replies.

  • Loading branch information...
commit 52aaf276c0eac89d9339f93664bfb476c24ec80f 1 parent f14f06e
@jdavisp3 jdavisp3 authored
Showing with 81 additions and 17 deletions.
  1. +20 −17 txredis/protocol.py
  2. +61 −0 txredis/test/test_redis.py
View
37 txredis/protocol.py
@@ -116,8 +116,7 @@ def __init__(self, db=None, password=None, charset='utf8',
self._buffer = ''
self._bulk_length = None
self._disconnected = False
- self._multi_bulk_length = None
- self._multi_bulk_reply = []
+ self._multi_bulk_stack = deque() # [[length-remaining, [replies] | None]]
self._request_queue = deque()
def dataReceived(self, data):
@@ -179,18 +178,20 @@ def dataReceived(self, data):
elif reply_type == self.MULTI_BULK:
# reply_data will contain the # of bulks we're about to get
try:
- self._multi_bulk_length = int(reply_data)
+ multi_bulk_length = int(reply_data)
except ValueError:
r = InvalidResponse("Cannot convert data '%s' to integer"
% reply_data)
self.responseReceived(r)
return
- if self._multi_bulk_length == -1:
- self._multi_bulk_reply = None
+ if multi_bulk_length == -1:
+ self._multi_bulk_stack.append([-1, None])
self.multiBulkDataReceived()
return
- elif self._multi_bulk_length == 0:
- self.multiBulkDataReceived()
+ else:
+ self._multi_bulk_stack.append([multi_bulk_length, []])
+ if multi_bulk_length == 0:
+ self.multiBulkDataReceived()
def failRequests(self, reason):
while self._request_queue:
@@ -255,9 +256,10 @@ def singleLineReceived(self, data):
self.responseReceived(reply)
def handleMultiBulkElement(self, element):
- self._multi_bulk_reply.append(element)
- self._multi_bulk_length = self._multi_bulk_length - 1
- if self._multi_bulk_length == 0:
+ top = self._multi_bulk_stack[-1]
+ top[1].append(element)
+ top[0] -= 1
+ if top[0] == 0:
self.multiBulkDataReceived()
def integerReceived(self, data):
@@ -267,7 +269,7 @@ def integerReceived(self, data):
except ValueError:
reply = InvalidResponse("Cannot convert data '%s' to integer"
% data)
- if self._multi_bulk_length > 0:
+ if self._multi_bulk_stack:
self.handleMultiBulkElement(reply)
return
@@ -282,13 +284,14 @@ def multiBulkDataReceived(self):
"""Multi bulk response received.
The bulks making up this response have been collected in
- self._multi_bulk_reply.
+ the last entry in self._multi_bulk_stack.
"""
- reply = self._multi_bulk_reply
- self._multi_bulk_reply = []
- self._multi_bulk_length = None
- self.handleCompleteMultiBulkData(reply)
+ reply = self._multi_bulk_stack.pop()[1]
+ if self._multi_bulk_stack:
+ self.handleMultiBulkElement(reply)
+ else:
+ self.handleCompleteMultiBulkData(reply)
def handleCompleteMultiBulkData(self, reply):
self.responseReceived(reply)
@@ -300,7 +303,7 @@ def responseReceived(self, reply):
provide the reply to the waiting request.
"""
- if self._multi_bulk_length > 0:
+ if self._multi_bulk_stack:
self.handleMultiBulkElement(reply)
elif self._request_queue:
self._request_queue.popleft().callback(reply)
View
61 txredis/test/test_redis.py
@@ -1453,6 +1453,67 @@ def test_large_multibulk(self):
t(res, set(map(str, data)))
+class MultiBulk(CommandsTestBase):
+ @defer.inlineCallbacks
+ def test_nested_multibulk(self):
+ r = self.redis
+ t = self.assertEqual
+
+ yield r.delete('str1', 'str2', 'list1', 'list2')
+ yield r.set('str1', 'str1')
+ yield r.set('str2', 'str2')
+ yield r.lpush('list1', 'b1')
+ yield r.lpush('list1', 'a1')
+ yield r.lpush('list2', 'b2')
+ yield r.lpush('list2', 'a2')
+
+ r.multi()
+ r.get('str1')
+ r.lrange('list1', 0, -1)
+ r.get('str2')
+ r.lrange('list2', 0, -1)
+ r.get('notthere')
+
+ a = yield r.execute()
+ ex = ['str1', ['a1', 'b1'], 'str2', ['a2', 'b2'], None]
+ t(a, ex)
+
+ a = yield r.get('str2')
+ ex = 'str2'
+ t(a, ex)
+
+ @defer.inlineCallbacks
+ def test_empty_multibulk(self):
+ r = self.redis
+ t = self.assertEqual
+
+ yield r.delete('list1')
+ a = yield r.lrange('list1', 0, -1)
+ ex = []
+ t(a, ex)
+
+ @defer.inlineCallbacks
+ def test_null_multibulk(self):
+ r = self.redis
+ t = self.assertEqual
+
+ clientCreator = protocol.ClientCreator(reactor, self.protocol)
+ r2 = yield clientCreator.connectTCP(REDIS_HOST, REDIS_PORT)
+
+ yield r.delete('a')
+
+ r.watch('a')
+ r.multi()
+ yield r.set('a', 'a')
+ yield r2.set('a', 'b')
+
+ r2.transport.loseConnection()
+
+ a = yield r.execute()
+ ex = None
+ t(a, ex)
+
+
class SortedSet(CommandsTestBase):
"""Test commands that operate on sorted sets.
"""
Please sign in to comment.
Something went wrong with that request. Please try again.