Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

implemented the parse_replies method for parsing pipelined responses.

  • Loading branch information...
commit 2255f807cb48eefc830dc4c642d71aba5fb9d37a 1 parent ceffe35
@agentzh agentzh authored
View
4 Makefile
@@ -32,6 +32,8 @@ CC=gcc
INSTALL=cp -p
all: parser.so
+ if [ ! -d redis ]; then mkdir redis; fi
+ cp parser.so redis/
parser.lo: redis-parser.c ddebug.h
$(CC) $(CFLAGS) -o parser.lo -c $<
@@ -47,8 +49,6 @@ clean:
$(RM) *.so *.lo lz/*.so
test: parser.so
- if [ ! -d redis ]; then mkdir redis; fi
- cp parser.so redis/
prove -r t
valtest: parser.so
View
103 redis-parser.c
@@ -1,4 +1,4 @@
-#define DDEBUG 0
+#define DDEBUG 1
#include "ddebug.h"
#include <lua.h>
@@ -27,13 +27,15 @@ enum {
static char *redis_null = "null";
+static int parse_reply_helper(lua_State *L, char **src, size_t len);
static int redis_parse_reply(lua_State *L);
+static int redis_parse_replies(lua_State *L);
static int redis_build_query(lua_State *L);
static const char * parse_single_line_reply(const char *src, const char *last,
size_t *dst_len);
static const char * parse_bulk_reply(const char *src, const char *last,
size_t *dst_len);
-static int parse_multi_bulk_reply(lua_State *L, const char *src,
+static int parse_multi_bulk_reply(lua_State *L, char **src,
const char *last);
static size_t get_num_size(size_t i);
static char *sprintf_num(char *dst, int64_t ui64);
@@ -41,6 +43,7 @@ static char *sprintf_num(char *dst, int64_t ui64);
static const struct luaL_Reg redis_parser[] = {
{"parse_reply", redis_parse_reply},
+ {"parse_replies", redis_parse_replies},
{"build_query", redis_build_query},
{NULL, NULL}
};
@@ -79,19 +82,79 @@ luaopen_redis_parser(lua_State *L)
static int
redis_parse_reply(lua_State *L)
{
- const char *p, *last;
+ char *p;
size_t len;
- const char *dst;
- size_t dst_len;
- lua_Number num;
- int rc;
if (lua_gettop(L) != 1) {
return luaL_error(L, "expected one argument but got %d",
lua_gettop(L));
}
- p = luaL_checklstring(L, 1, &len);
+ p = (char *) luaL_checklstring(L, 1, &len);
+
+ return parse_reply_helper(L, &p, len);
+}
+
+
+static int
+redis_parse_replies(lua_State *L)
+{
+ char *p;
+ char *q;
+ int i, n, nret;
+ size_t len;
+
+ if (lua_gettop(L) != 2) {
+ return luaL_error(L, "expected two arguments but got %d",
+ lua_gettop(L));
+ }
+
+ p = (char *) luaL_checklstring(L, 1, &len);
+
+ n = luaL_checknumber(L, 2);
+ dd("n = %d", n);
+
+ lua_pop(L, 1);
+
+ lua_createtable(L, n, 0); /* table */
+
+ for (i = 1; i <= n; i++) {
+ dd("parsing reply %d", i);
+
+ lua_createtable(L, n, 2); /* table table */
+ q = p;
+ nret = parse_reply_helper(L, &p, len); /* table table res typ */
+ if (nret != 2) {
+ return luaL_error(L, "internal error: redis_parse_reply "
+ "returns %d", nret);
+ }
+
+ dd("p = %p, q = %p, len = %d", p, q, (int) len);
+
+ len -= p - q;
+
+ dd("len is now %d", (int) len);
+
+ lua_rawseti(L, -3, 2); /* table table res */
+ lua_rawseti(L, -2, 1); /* table table */
+ lua_rawseti(L, -2, i); /* table */
+ }
+
+ return 1;
+}
+
+
+static int
+parse_reply_helper(lua_State *L, char **src, size_t len)
+{
+ char *p;
+ const char *last;
+ const char *dst;
+ size_t dst_len;
+ lua_Number num;
+ int rc;
+
+ p = *src;
if (len == 0) {
lua_pushliteral(L, "empty reply");
@@ -112,6 +175,8 @@ redis_parse_reply(lua_State *L)
return 2;
}
+ *src += dst_len + 1 + sizeof("\r\n") - 1;
+
lua_pushlstring(L, dst, dst_len);
lua_pushnumber(L, STATUS_REPLY);
break;
@@ -126,6 +191,8 @@ redis_parse_reply(lua_State *L)
return 2;
}
+ *src += dst_len + 1 + sizeof("\r\n") - 1;
+
lua_pushlstring(L, dst, dst_len);
lua_pushnumber(L, ERROR_REPLY);
break;
@@ -140,8 +207,12 @@ redis_parse_reply(lua_State *L)
return 2;
}
+ *src += dst_len + 1 + sizeof("\r\n") - 1;
+
lua_pushlstring(L, dst, dst_len);
num = lua_tonumber(L, -1);
+ lua_pop(L, 1);
+
lua_pushnumber(L, num);
lua_pushnumber(L, INTEGER_REPLY);
break;
@@ -157,18 +228,22 @@ redis_parse_reply(lua_State *L)
}
if (dst_len == -1) {
+ *src = (char *) dst + sizeof("\r\n") - 1;
+
lua_pushnil(L);
lua_pushnumber(L, BULK_REPLY);
return 2;
}
+ *src = (char *) dst + dst_len + sizeof("\r\n") - 1;
+
lua_pushlstring(L, dst, dst_len);
lua_pushnumber(L, BULK_REPLY);
break;
case '*':
p++;
- rc = parse_multi_bulk_reply(L, p, last);
+ rc = parse_multi_bulk_reply(L, &p, last);
if (rc != PARSE_OK) {
lua_pushliteral(L, "bad multi bulk reply");
@@ -178,11 +253,13 @@ redis_parse_reply(lua_State *L)
/* rc == PARSE_OK */
+ *src = (char *) p;
+
lua_pushnumber(L, MULTI_BULK_REPLY);
break;
default:
- lua_pushliteral(L, "empty reply");
+ lua_pushliteral(L, "invalid reply");
lua_pushnumber(L, BAD_REPLY);
break;
}
@@ -312,9 +389,9 @@ parse_bulk_reply(const char *src, const char *last, size_t *dst_len)
static int
-parse_multi_bulk_reply(lua_State *L, const char *src, const char *last)
+parse_multi_bulk_reply(lua_State *L, char **src, const char *last)
{
- const char *p = src;
+ const char *p = *src;
int count = 0;
int i;
size_t dst_len;
@@ -378,6 +455,8 @@ parse_multi_bulk_reply(lua_State *L, const char *src, const char *last)
lua_rawseti(L, -2, i);
}
+ *src = (char *) p;
+
return PARSE_OK;
invalid:
View
62 t/RedisParser.pm
@@ -0,0 +1,62 @@
+package t::RedisParser;
+
+use Test::Base -Base;
+use IPC::Run3;
+use Cwd;
+
+use Test::LongString;
+
+our @EXPORT = qw( run_tests );
+
+$ENV{LUA_CPATH} = "?.so;" . ($ENV{LUA_CPATH} || "") . ';' . "/home/lz/luax/?.so;;";
+#$ENV{LUA_PATH} = ($ENV{LUA_PATH} || "" ) . ';' . getcwd . "/runtime/?.lua" . ';;';
+
+sub run_test ($) {
+ my $block = shift;
+ #print $json_xs->pretty->encode(\@new_rows);
+ #my $res = #print $json_xs->pretty->encode($res);
+ my $name = $block->name;
+
+ my $lua = $block->lua or
+ die "No --- lua specified for test $name\n";
+
+ open my $fh, ">test_case.lua";
+ print $fh $lua;
+ close $fh;
+
+ my ($res, $err);
+
+ my @cmd;
+
+ if ($ENV{TEST_LUA_USE_VALGRIND}) {
+ @cmd = ('valgrind', '-q', '--leak-check=full', 'lua', 'test_case.lua');
+ } else {
+ @cmd = ('lua', 'test_case.lua');
+ }
+
+ run3 \@cmd, undef, \$res, \$err;
+
+ #warn "res:$res\nerr:$err\n";
+
+ if (defined $block->err) {
+ $err =~ /.*:.*:.*: (.*\s)?/;
+ $err = $1;
+ is $err, $block->err, "$name - err expected";
+ } elsif ($?) {
+ die "Failed to execute --- lua for test $name: $err\n";
+
+ } else {
+ #is $res, $block->out, "$name - output ok";
+ is_string $res, $block->out, "$name - output ok";
+ }
+
+ unlink 'test_case.lua' or warn "could not delete \'test_case.lua\':$!";
+}
+
+sub run_tests () {
+ for my $block (blocks()) {
+ run_test($block);
+ }
+}
+
+1;
View
257 t/pipeline.t
@@ -0,0 +1,257 @@
+# vi:ft=
+
+use strict;
+use warnings;
+
+use t::RedisParser;
+plan tests => 1 * blocks();
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: single reply parsed by parse_replies
+--- lua
+parser = require("redis.parser")
+replies = '+OK\r\n'
+results = parser.parse_replies(replies, 1)
+print("res count == " .. #results)
+print("res[1] count == " .. #results[1])
+local res = results[1]
+print("res[1][1] == " .. res[1])
+print("res[1][2] == " .. res[2] .. ' == ' .. parser.STATUS_REPLY)
+--- out
+res count == 1
+res[1] count == 2
+res[1][1] == OK
+res[1][2] == 1 == 1
+
+
+
+=== TEST 2: single bad reply parsed by parse_replies
+--- lua
+parser = require("redis.parser")
+replies = '+OK'
+results = parser.parse_replies(replies, 1)
+print("res count == " .. #results)
+print("res[1] count == " .. #results[1])
+local res = results[1]
+print("res[1][1] == " .. res[1])
+print("res[1][2] == " .. res[2] .. ' == ' .. parser.BAD_REPLY)
+--- out
+res count == 1
+res[1] count == 2
+res[1][1] == bad status reply
+res[1][2] == 0 == 0
+
+
+
+=== TEST 3: multiple status replies
+--- lua
+parser = require("redis.parser")
+replies = '+OK\r\n+DONE\r\n'
+results = parser.parse_replies(replies, 2)
+print("res count == " .. #results)
+print("res[1] count == " .. #results[1])
+print("res[2] count == " .. #results[2])
+
+local res = results[1]
+
+print("res[1][1] == " .. res[1])
+print("res[1][2] == " .. res[2] .. ' == ' .. parser.STATUS_REPLY)
+
+res = results[2]
+
+print("res[2][1] == " .. res[1])
+print("res[2][2] == " .. res[2] .. ' == ' .. parser.STATUS_REPLY)
+
+--- out
+res count == 2
+res[1] count == 2
+res[2] count == 2
+res[1][1] == OK
+res[1][2] == 1 == 1
+res[2][1] == DONE
+res[2][2] == 1 == 1
+
+
+
+=== TEST 4: multiple integer replies
+--- lua
+parser = require("redis.parser")
+replies = ':-32\r\n:532\r\n'
+results = parser.parse_replies(replies, 2)
+print("res count == " .. #results)
+print("res[1] count == " .. #results[1])
+print("res[2] count == " .. #results[2])
+
+local res = results[1]
+
+print("res[1][1] == " .. res[1])
+print("res[1][2] == " .. res[2] .. ' == ' .. parser.INTEGER_REPLY)
+
+res = results[2]
+
+print("res[2][1] == " .. res[1])
+print("res[2][2] == " .. res[2] .. ' == ' .. parser.INTEGER_REPLY)
+
+--- out
+res count == 2
+res[1] count == 2
+res[2] count == 2
+res[1][1] == -32
+res[1][2] == 3 == 3
+res[2][1] == 532
+res[2][2] == 3 == 3
+
+
+
+=== TEST 5: multiple error replies
+--- lua
+parser = require("redis.parser")
+replies = '-ERROR\r\n-BAD\r\n'
+results = parser.parse_replies(replies, 2)
+print("res count == " .. #results)
+print("res[1] count == " .. #results[1])
+print("res[2] count == " .. #results[2])
+
+local res = results[1]
+
+print("res[1][1] == " .. res[1])
+print("res[1][2] == " .. res[2] .. ' == ' .. parser.ERROR_REPLY)
+
+res = results[2]
+
+print("res[2][1] == " .. res[1])
+print("res[2][2] == " .. res[2] .. ' == ' .. parser.ERROR_REPLY)
+
+--- out
+res count == 2
+res[1] count == 2
+res[2] count == 2
+res[1][1] == ERROR
+res[1][2] == 2 == 2
+res[2][1] == BAD
+res[2][2] == 2 == 2
+
+
+
+=== TEST 6: multiple bad replies (invalid reply)
+--- lua
+parser = require("redis.parser")
+replies = '\r\n-BAD\r\n'
+results = parser.parse_replies(replies, 2)
+print("res count == " .. #results)
+print("res[1] count == " .. #results[1])
+print("res[2] count == " .. #results[2])
+
+local res = results[1]
+
+print("res[1][1] == " .. res[1])
+print("res[1][2] == " .. res[2] .. ' == ' .. parser.BAD_REPLY)
+
+res = results[2]
+
+print("res[2][1] == " .. res[1])
+print("res[2][2] == " .. res[2] .. ' == ' .. parser.BAD_REPLY)
+
+--- out
+res count == 2
+res[1] count == 2
+res[2] count == 2
+res[1][1] == invalid reply
+res[1][2] == 0 == 0
+res[2][1] == invalid reply
+res[2][2] == 0 == 0
+
+
+
+=== TEST 7: multiple bad replies (empty reply)
+--- lua
+parser = require("redis.parser")
+replies = ''
+results = parser.parse_replies(replies, 2)
+print("res count == " .. #results)
+print("res[1] count == " .. #results[1])
+print("res[2] count == " .. #results[2])
+
+local res = results[1]
+
+print("res[1][1] == " .. res[1])
+print("res[1][2] == " .. res[2] .. ' == ' .. parser.BAD_REPLY)
+
+res = results[2]
+
+print("res[2][1] == " .. res[1])
+print("res[2][2] == " .. res[2] .. ' == ' .. parser.BAD_REPLY)
+
+--- out
+res count == 2
+res[1] count == 2
+res[2] count == 2
+res[1][1] == empty reply
+res[1][2] == 0 == 0
+res[2][1] == empty reply
+res[2][2] == 0 == 0
+
+
+
+=== TEST 8: multiple bulk replies
+--- lua
+parser = require("redis.parser")
+replies = '$-1\r\n$5\r\nhello\r\n'
+results = parser.parse_replies(replies, 2)
+print("res count == " .. #results)
+print("res[1] count == " .. #results[1])
+print("res[2] count == " .. #results[2])
+
+local res = results[1]
+
+print("res[1][1] == " .. (res[1] or "nil"))
+print("res[1][2] == " .. res[2] .. ' == ' .. parser.BULK_REPLY)
+
+res = results[2]
+
+print("res[2][1] == " .. res[1])
+print("res[2][2] == " .. res[2] .. ' == ' .. parser.BULK_REPLY)
+
+--- out
+res count == 2
+res[1] count == 2
+res[2] count == 2
+res[1][1] == nil
+res[1][2] == 4 == 4
+res[2][1] == hello
+res[2][2] == 4 == 4
+
+
+
+=== TEST 9: multiple multi-bulk replies
+--- lua
+yajl = require('yajl')
+parser = require("redis.parser")
+replies = '*2\r\n$-1\r\n$5\r\nhello\r\n*1\r\n$1\r\na\r\n$2\r\nef\r\n'
+results = parser.parse_replies(replies, 2)
+print("res count == " .. #results)
+print("res[1] count == " .. #results[1])
+print("res[2] count == " .. #results[2])
+
+local res = results[1]
+
+print("res[1][1] == " .. yajl.to_string(res[1]))
+print("res[1][2] == " .. res[2] .. ' == ' .. parser.MULTI_BULK_REPLY)
+
+res = results[2]
+
+print("res[2][1] == " .. yajl.to_string(res[1]))
+print("res[2][2] == " .. res[2] .. ' == ' .. parser.MULTI_BULK_REPLY)
+
+--- out
+res count == 2
+res[1] count == 2
+res[2] count == 2
+res[1][1] == [null,"hello"]
+res[1][2] == 5 == 5
+res[2][1] == ["a"]
+res[2][2] == 5 == 5
+
View
81 t/sanity.t
@@ -3,61 +3,14 @@
use strict;
use warnings;
-use Test::Base;
-use IPC::Run3;
-use Cwd;
-
-use Test::LongString;
-
+use t::RedisParser;
plan tests => 1 * blocks();
-$ENV{LUA_CPATH} = ($ENV{LUA_CPATH} || "") . ';' . "/home/lz/luax/?.so;;";
-#$ENV{LUA_PATH} = ($ENV{LUA_PATH} || "" ) . ';' . getcwd . "/runtime/?.lua" . ';;';
-
-run {
- #print $json_xs->pretty->encode(\@new_rows);
- #my $res = #print $json_xs->pretty->encode($res);
- my $block = shift;
- my $name = $block->name;
-
- my $lua = $block->lua or
- die "No --- lua specified for test $name\n";
-
- open my $fh, ">test_case.lua";
- print $fh $lua;
- close $fh;
-
- my ($res, $err);
-
- my @cmd;
-
- if ($ENV{TEST_LUA_USE_VALGRIND}) {
- @cmd = ('valgrind', '-q', '--leak-check=full', 'lua', 'test_case.lua');
- } else {
- @cmd = ('lua', 'test_case.lua');
- }
-
- run3 \@cmd, undef, \$res, \$err;
-
- #warn "res:$res\nerr:$err\n";
-
- if (defined $block->err) {
- $err =~ /.*:.*:.*: (.*\s)?/;
- $err = $1;
- is $err, $block->err, "$name - err expected";
- } elsif ($?) {
- die "Failed to execute --- lua for test $name: $err\n";
- } else {
- #is $res, $block->out, "$name - output ok";
- is_string $res, $block->out, "$name - output ok";
- }
- unlink 'test_case.lua' or warn "could not delete \'test_case.lua\':$!";
-}
+run_tests();
__DATA__
=== TEST 1: no crlf in status reply
---- sql
--- lua
parser = require("redis.parser")
reply = '+OK'
@@ -71,7 +24,6 @@ res == bad status reply
=== TEST 2: good status reply
---- sql
--- lua
parser = require("redis.parser")
reply = '+OK\r\n'
@@ -85,7 +37,6 @@ res == OK
=== TEST 3: good error reply
---- sql
--- lua
parser = require("redis.parser")
reply = '-Bad argument\rHey\r\nblah blah blah\r\n'
@@ -99,7 +50,6 @@ res == Bad argument\rHey\n"
=== TEST 4: good integer reply
---- sql
--- lua
parser = require("redis.parser")
reply = ':-32\r\n'
@@ -115,7 +65,6 @@ res type == number
=== TEST 5: non-numeric integer reply
---- sql
--- lua
parser = require("redis.parser")
reply = ':abc\r\n'
@@ -131,7 +80,6 @@ res type == number
=== TEST 6: bad integer reply
---- sql
--- lua
parser = require("redis.parser")
reply = ':12\r'
@@ -147,7 +95,6 @@ res type == string
=== TEST 7: good bulk reply
---- sql
--- lua
parser = require("redis.parser")
reply = '$5\r\nhello\r\n'
@@ -161,7 +108,6 @@ res == hello
=== TEST 8: good bulk reply (ignoring trailing stuffs)
---- sql
--- lua
parser = require("redis.parser")
reply = '$5\r\nhello\r\nblah'
@@ -175,7 +121,6 @@ res == hello
=== TEST 9: bad bulk reply (bad bulk size)
---- sql
--- lua
parser = require("redis.parser")
reply = '$3b\r\nhello\r\nblah'
@@ -189,7 +134,6 @@ res == bad bulk reply
=== TEST 10: bad bulk reply (bulk size too small)
---- sql
--- lua
parser = require("redis.parser")
reply = '$3\r\nhello\r\nblah'
@@ -203,7 +147,6 @@ res == bad bulk reply
=== TEST 11: bad bulk reply (bulk size too large)
---- sql
--- lua
parser = require("redis.parser")
reply = '$6\r\nhello\r\nblah'
@@ -217,7 +160,6 @@ res == bad bulk reply
=== TEST 12: bad bulk reply (bulk size too large, 2)
---- sql
--- lua
parser = require("redis.parser")
reply = '$7\r\nhello\r\nblah'
@@ -231,7 +173,6 @@ res == bad bulk reply
=== TEST 13: bad bulk reply (bulk size too large, 3)
---- sql
--- lua
parser = require("redis.parser")
reply = '$8\r\nhello\r\nblah'
@@ -245,7 +186,6 @@ res == bad bulk reply
=== TEST 14: good bulk reply (nil value)
---- sql
--- lua
parser = require("redis.parser")
reply = '$-1\r\n'
@@ -259,7 +199,6 @@ res\tnil\n"
=== TEST 15: good bulk reply (nil value, -25 size)
---- sql
--- lua
parser = require("redis.parser")
reply = '$-25\r\n'
@@ -273,7 +212,6 @@ res\tnil\n"
=== TEST 16: bad bulk reply (nil value, -1 size)
---- sql
--- lua
parser = require("redis.parser")
reply = '$-1\r'
@@ -287,7 +225,6 @@ res\tbad bulk reply\n"
=== TEST 17: bad bulk reply (nil value, -1 size)
---- sql
--- lua
parser = require("redis.parser")
reply = '$-1\ra'
@@ -301,7 +238,6 @@ res\tbad bulk reply\n"
=== TEST 18: bad bulk reply (nil value, -1 size)
---- sql
--- lua
parser = require("redis.parser")
reply = '$-1ab'
@@ -315,7 +251,6 @@ res\tbad bulk reply\n"
=== TEST 19: good multi bulk reply (1 bulk)
---- sql
--- lua
yajl = require('yajl')
parser = require("redis.parser")
@@ -330,7 +265,6 @@ res == ["a"]\n}
=== TEST 20: good multi bulk reply (4 bulks)
---- sql
--- lua
yajl = require('yajl')
parser = require("redis.parser")
@@ -345,7 +279,6 @@ res == ["a",null,"","hello"]\n}
=== TEST 21: bad multi bulk reply (4 bulks)
---- sql
--- lua
yajl = require('yajl')
parser = require("redis.parser")
@@ -360,7 +293,6 @@ res == "bad multi bulk reply"\n}
=== TEST 22: bad multi bulk reply (4 bulks)
---- sql
--- lua
yajl = require('yajl')
parser = require("redis.parser")
@@ -375,7 +307,6 @@ res == "bad multi bulk reply"\n}
=== TEST 23: bad multi bulk reply (4 bulks)
---- sql
--- lua
yajl = require('yajl')
parser = require("redis.parser")
@@ -390,7 +321,6 @@ res == "bad multi bulk reply"\n}
=== TEST 24: bad multi bulk reply (4 bulks)
---- sql
--- lua
yajl = require('yajl')
parser = require("redis.parser")
@@ -405,7 +335,6 @@ res == "bad multi bulk reply"\n}
=== TEST 25: build query (empty param table)
---- sql
--- lua
yajl = require('yajl')
parser = require("redis.parser")
@@ -418,7 +347,6 @@ empty input param table
=== TEST 26: build query (single param)
---- sql
--- lua
yajl = require('yajl')
parser = require("redis.parser")
@@ -431,7 +359,6 @@ query == "*1\r\n$4\r\nping\r\n"
=== TEST 27: build query (single param)
---- sql
--- lua
yajl = require('yajl')
parser = require("redis.parser")
@@ -444,7 +371,6 @@ query == "*3\r\n$3\r\nget\r\n$3\r\none\r\n$2\r\n\r\n\r\n"
=== TEST 28: build query (empty param "")
---- sql
--- lua
yajl = require('yajl')
parser = require("redis.parser")
@@ -457,7 +383,6 @@ query == "*1\r\n$0\r\n\r\n"
=== TEST 29: build query (empty param "")
---- sql
--- lua
yajl = require('yajl')
parser = require("redis.parser")
@@ -470,7 +395,6 @@ query == "*1\r\n$0\r\n\r\n"
=== TEST 30: build query (nil param)
---- sql
--- lua
yajl = require('yajl')
parser = require("redis.parser")
@@ -483,7 +407,6 @@ query == "*1\r\n$-1\r\n"
=== TEST 31: build query (numeric param)
---- sql
--- lua
yajl = require('yajl')
parser = require("redis.parser")
Please sign in to comment.
Something went wrong with that request. Please try again.