Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

lua tests in place

  • Loading branch information...
commit 8e89a19ced7f01110c13a3fbbfdb7f60fade3efa 1 parent 19e3667
quantmind authored
View
12 examples/models.py
@@ -11,13 +11,13 @@ def something(self):
class SimpleModel(odm.StdModel):
- code = odm.SymbolField(unique = True)
- group = odm.SymbolField(required = False)
+ code = odm.SymbolField(unique=True)
+ group = odm.SymbolField(required=False)
description = odm.CharField()
somebytes = odm.ByteField()
- object = odm.PickleObjectField(required = False)
- cached_data = odm.ByteField(as_cache = True)
- timestamp = odm.DateTimeField(as_cache = True)
+ object = odm.PickleObjectField(required=False)
+ cached_data = odm.ByteField(as_cache=True)
+ timestamp = odm.DateTimeField(as_cache=True)
objects = CustomManager()
@@ -28,7 +28,7 @@ def __unicode__(self):
#####################################################################
# FINANCE APPLICATION
class Base(odm.StdModel):
- name = odm.SymbolField(unique = True)
+ name = odm.SymbolField(unique=True)
ccy = odm.SymbolField()
def __unicode__(self):
View
272 examples/redis.lua
@@ -0,0 +1,272 @@
+-- A Stand-alone REDIS server implementation for testing redis lua scripts
+-- outside redis.
+--
+-- To use it
+-- redis = require("redis")
+-- redis.call(...
+--
+local NUM_DATABASES = 16
+local database = 1
+local store = {}
+local redis = {
+ bad_key = 'Redis Error: Operation performed against wrong type of key',
+ ok = {ok='OK'}
+}
+
+local flush_all = function ()
+ store = {}
+ for i=1, NUM_DATABASES do
+ store[i] = {}
+ end
+end
+flush_all()
+
+local function encode (v)
+ if v then
+ return v .. ''
+ else
+ return ''
+ end
+end
+
+local function noencode(v)
+ return v
+end
+
+local function as_pairs(arg)
+ local ps, c, v0 = {}
+ for i, v in ipairs(arg) do
+ if 2*math.floor(i/2) == i then
+ table.insert(ps, {v0, v})
+ else
+ v0 = v
+ end
+ end
+ return ps
+end
+
+local meta = {
+ set = {name = 'set', encode = encode},
+ list = {name = 'list', encode = encode},
+ zset = {name = 'zset', encode = noencode},
+ hash = {name = 'hash', encode = noencode}
+}
+
+local function is(h, name)
+ return type(h) == 'table' and getmetatable(h).name == name
+end
+
+-- Utility for adding items to a list
+local function oper (type, key, callback, value, values)
+ local key = encode(key)
+ local h = store[database][key]
+ if not h then
+ h = setmetatable({}, meta[type])
+ end
+ if is(h, type) then
+ if callback then
+ local vencode, cbk = getmetatable(h).encode
+ store[database][key] = h
+ value = vencode(value)
+ cbk = callback(h, value)
+ if cbk ~= nil then
+ return cbk
+ elseif values then
+ for _, v in ipairs(values) do
+ callback(h, vencode(v))
+ end
+ end
+ end
+ if type == 'list' then
+ return # h
+ else
+ local count = 0
+ for _,_ in pairs(h) do
+ count = count + 1
+ end
+ return count
+ end
+ else
+ error(redis.bad_key)
+ end
+end
+
+--------------------------------------------------------------------------------
+-- REDIS COMMAND TABLE
+--------------------------------------------------------------------------------
+redis.commands = {
+ ----------------------------------------------------------------------------
+ -- KEY COMMANDS
+ del = function (...)
+ local count = 0
+ for _, key in ipairs(arg) do
+ key = encode(key)
+ if store[database][key] ~= nil then
+ store[database][key] = nil
+ count = count + 1
+ end
+ end
+ return count
+ end,
+ exists = function(key)
+ return store[database][encode(key)] ~= nil
+ end,
+ keys = function()
+ local all = {}
+ for name,_ in pairs(store[database]) do
+ table.insert(all, name)
+ end
+ return all
+ end,
+ ----------------------------------------------------------------------------
+ -- STRING COMMANDS
+ get = function (key)
+ local v = store[database][encode(key)]
+ if type(v) == 'table' then
+ error(redis.bad_key)
+ else
+ return v
+ end
+ end,
+ set = function (key, value)
+ store[database][encode(key)] = value
+ return redis.ok
+ end,
+ decr = function (key)
+ return redis.commands.incrby(key, -1)
+ end,
+ decrby = function (key, decrement)
+ return redis.commands.incrby(key, -decrement)
+ end,
+ incr = function (key)
+ return redis.commands.incrby(key, 1)
+ end,
+ incrby = function (key, increment)
+ local v = redis.commands.get(key)
+ if v then
+ v = v + increment
+ else
+ v = increment
+ end
+ redis.commands.set(key, v)
+ return v
+ end,
+ ----------------------------------------------------------------------------
+ -- LIST COMMANDS
+ llen = function(key)
+ return oper('list', key)
+ end,
+ lpush = function(key, value, ...)
+ return oper('list', key, function(h,v) table.insert(h,1,v) end, value, arg)
+ end,
+ rpush = function(key, value, ...)
+ return oper('list', key, function(h,v) table.insert(h,v) end, value, arg)
+ end,
+ ----------------------------------------------------------------------------
+ -- SET COMMANDS
+ sadd = function(key, value, ...)
+ local c = 0
+ oper('set', key, function(h, fi)
+ fi = encode(fi)
+ if h[fi] == nil then
+ c = c + 1
+ h[fi] = 1
+ end
+ end, value, arg)
+ return c
+ end,
+ scard = function(key)
+ return oper('set', key)
+ end,
+ srem = function(key, value, ...)
+ local c = 0
+ oper('set', key, function (h, fi)
+ fi = encode(fi)
+ if h[fi] ~= nil then
+ c = c + 1
+ h[fi] = nil
+ end
+ end, value, arg)
+ return c
+ end,
+ ----------------------------------------------------------------------------
+ -- HASH COMMANDS
+ hdel = function(key, field, ...)
+ local c = 0
+ oper('hash', key, function (h, fi)
+ fi = encode(fi)
+ if h[fi] ~= nil then
+ c = c + 1
+ h[fi] = nil
+ end
+ end, field, arg)
+ return c
+ end,
+ hexists = function(key, field)
+ return oper('hash', key, function (h, field)
+ return h[field] ~= nil
+ end, field)
+ end,
+ hget = function(key, field)
+ return store[database][key][field]
+ end,
+ hgetall = function(key)
+ return store[database][key]
+ end,
+ hset = function(key, field, value)
+ return redis.commands.hmset(key, field, value)
+ end,
+ hmset = function(key, field, value, ...)
+ local field_values = as_pairs(arg)
+ local function _insert(h, field_value)
+ local field, value = unpack(field_value)
+ h[encode(field)] = encode(value)
+ end
+ return oper('hash', key, _insert, {field, value}, field_values)
+ end,
+ hset = function(key, field, value)
+ return redis.commands.hmset(key, field, value)
+ end,
+ hsetnx = function(key, field, value)
+ if redis.commands.hexists(key, field) == false then
+ redis.commands.hset(key, field, value)
+ return 1
+ else
+ return 0
+ end
+ end,
+ ----------------------------------------------------------------------------
+ -- SERVER COMMANDS
+ dbsize = function ()
+ local count = 0
+ for _,v in pairs(store[database]) do
+ count = count + 1
+ end
+ return count
+ end,
+ flushall = function ()
+ flush_all()
+ return redis.ok
+ end,
+ flushdb = function ()
+ store[database] = {}
+ return redis.ok
+ end,
+ select = function (db)
+ if db >= 0 and db < NUM_DATABASES then
+ database = db+1
+ end
+ return redis.ok
+ end
+}
+
+redis.call = function (command, ...)
+ local com = redis.commands[string.lower(command)]
+ if com then
+ return com(unpack(arg))
+ else
+ error('Unknown redis command ' .. command)
+ end
+end
+
+return redis
View
8 luatests.lua
@@ -3,14 +3,18 @@
-- To run tests simply
-- lua luatests.lua
--
-package.path = package.path .. ";stdnet/lib/lua/?.lua"
+package.path = package.path .. ";stdnet/lib/lua/?.lua;tests/lua/?.lua"
+-- To run the debugger in eclipse you need to install the DBGp Client
+-- http://wiki.eclipse.org/Koneki/LDT/User_Guide/Concepts/Debugger#Source_Mapping
+-- Then create a lua project where to run the debug server
+pcall(function() require("debugger")() end)
+
require("lunatest")
print '=============================='
print('To run just some tests, add "-t [pattern]"')
print '=============================='
lunatest.suite("tests/lua/utils")
-lunatest.suite("tests/lua/redis_mock")
lunatest.suite("tests/lua/odm")
lunatest.suite("tests/lua/columnts")
View
6 stdnet/lib/lua/columnts/columnts.lua
@@ -512,3 +512,9 @@ function columnts:merge(key, elements, fields)
end
return result
end
+
+
+-- Return the module only when this module is not in REDIS
+if not (KEYS and ARGV) then
+ return columnts
+end
View
4 stdnet/lib/lua/columnts/stats.lua
@@ -170,3 +170,7 @@ statistics.multivariate = function (series)
end
end
+-- Return the module only when this module is not in REDIS
+if not (KEYS and ARGV) then
+ return statistics
+end
View
79 stdnet/lib/lua/odm.lua
@@ -1,6 +1,7 @@
local AUTO_ID, COMPOSITE_ID, CUSTOM_ID = 1, 2, 3
-- odm namespace - object-data mapping
local odm = {
+ redis=nil,
TMP_KEY_LENGTH = 12,
ModelMeta = {
namespace = '',
@@ -49,8 +50,9 @@ odm.Model = {
--[[
Commit an instance to redis
num: number of instances to commit
- args: tuable containing data to save. The data is on the form
+ args: table containing data to save. The data is on the form
action, id, score, data_length
+ @return an array of id saved to the database
--]]
commit = function (self, num, args)
local count, p, results = 0, 0, {}
@@ -66,7 +68,7 @@ odm.Model = {
end,
--[[
Build a new query and store the resulting ids into a temporary set.
- It returns a dictionary containg the temporary set key and the
+ It returns a dictionary containing the temporary set key and the
size of the temporary set
--]]
query = function (self, field, tmp, queries)
@@ -130,7 +132,7 @@ odm.Model = {
chars[loop] = string.char(math.random(1, 255))
end
local key = bk .. table.concat(chars)
- if redis.call('exists', key) + 0 == 0 then
+ if odm.redis.call('exists', key) + 0 == 0 then
return key
end
end
@@ -138,36 +140,36 @@ odm.Model = {
--
setsize = function(self, setid)
if self.meta.sorted then
- return redis.call('zcard', setid)
+ return odm.redis.call('zcard', setid)
else
- return redis.call('scard', setid)
+ return odm.redis.call('scard', setid)
end
end,
--
setids = function(self, setid)
if self.meta.sorted then
- return redis.call('zrange', setid, 0, -1)
+ return odm.redis.call('zrange', setid, 0, -1)
else
- return redis.call('smembers', setid)
+ return odm.redis.call('smembers', setid)
end
end,
--
setadd = function(self, setid, score, id, autoincr)
if autoincr then
- score = redis.call('zincrby', setid, score, id)
+ score = odm.redis.call('zincrby', setid, score, id)
elseif self.meta.sorted then
- redis.call('zadd', setid, score, id)
+ odm.redis.call('zadd', setid, score, id)
else
- redis.call('sadd', setid, id)
+ odm.redis.call('sadd', setid, id)
end
return score
end,
--
setrem = function(self, setid, id)
if self.meta.sorted then
- redis.call('zrem', setid, id)
+ odm.redis.call('zrem', setid, id)
else
- redis.call('srem', setid, id)
+ odm.redis.call('srem', setid, id)
end
end,
--
@@ -177,7 +179,7 @@ odm.Model = {
elseif field_type == 'u' then
local mapkey, ids = self:mapkey(field), self:setids(setid)
for _, v in ipairs(ids) do
- add(redis.call('hget', mapkey, v))
+ add(odm.redis.call('hget', mapkey, v))
end
elseif field_type == 'i' then
addset(setid, union)
@@ -191,7 +193,7 @@ odm.Model = {
add(value)
elseif field_type == 'u' then
local mapkey = self:mapkey(field)
- add(redis.call('hget', mapkey, value))
+ add(odm.redis.call('hget', mapkey, value))
elseif field_type == 'i' then
union(value)
else
@@ -210,7 +212,7 @@ odm.Model = {
end
else
for _, id in ipairs(ids) do
- v = redis.call('hget', self:object_key(id), field)
+ v = odm.redis.call('hget', self:object_key(id), field)
if range.selector(v, r.v1, r.v2) then
add(v)
end
@@ -224,12 +226,12 @@ odm.Model = {
if self.meta.id_type == AUTO_ID then
if id == '' then
created_id = true
- id = redis.call('incr', self.auto_ids)
+ id = odm.redis.call('incr', self.auto_ids)
else
id = id + 0 -- must be numeric
- local counter = redis.call('get', self.auto_ids)
+ local counter = odm.redis.call('get', self.auto_ids)
if not counter or counter + 0 < id then
- redis.call('set', self.auto_ids, id)
+ odm.redis.call('set', self.auto_ids, id)
end
end
end
@@ -238,10 +240,10 @@ odm.Model = {
else
local oldid, idkey, original_values = id, self:object_key(id), {}
if action == 'override' or action == 'change' then -- override or change
- original_values = redis.call('hgetall', idkey)
+ original_values = odm.redis.call('hgetall', idkey)
errors = self:update_indices(false, id, oldid, score)
if action == 'override' then
- redis.call('del', idkey)
+ odm.redis.call('del', idkey)
end
end
-- Composite ID. Calculate new ID and data
@@ -254,23 +256,22 @@ odm.Model = {
end
score = self:setadd(self.idset, score, id, self.meta.autoincr)
if # data > 0 then
- redis.call('hmset', idkey, unpack(data))
+ odm.redis.call('hmset', idkey, unpack(data))
end
errors = self:update_indices(true, id, oldid, score)
-- An error has occured. Rollback changes.
if # errors > 0 then
-- Remove indices
- self:update_indices(false, id, oldid)
+ self:update_indices('delete', id, oldid)
if action == 'add' then
- self:update_indices('delete', id)
if created_id then
- redis.call('decr', self.auto_ids)
+ odm.redis.call('decr', self.auto_ids)
id = ''
end
elseif # original_values > 0 then
id = oldid
idkey = self:object_key(id)
- redis.call('hmset', idkey, unpack(original_values))
+ odm.redis.call('hmset', idkey, unpack(original_values))
self:update_indices(true, id, oldid, score)
end
end
@@ -285,18 +286,21 @@ odm.Model = {
update_indices = function (self, oper, id, oldid, score)
local idkey, errors, idxkey = self:object_key(id), {}
for field, unique in pairs(self.meta.indices) do
- local value = redis.call('hget', idkey, field)
+ -- obtain the field value
+ local value = odm.redis.call('hget', idkey, field)
if unique then
idxkey = self:mapkey(field) -- id for the hash table mapping field value to instance ids
if oper == 'delete' then
- redis.call('hdel', idxkey, value)
+ if value then
+ odm.redis.call('hdel', idxkey, value)
+ end
elseif oper then
- if redis.call('hsetnx', idxkey, value, id) + 0 == 0 then
+ if odm.redis.call('hsetnx', idxkey, value, id) + 0 == 0 then
-- The value was already available!
- if oldid == id or not redis.call('hget', idxkey, value) == oldid then
+ if oldid == id or not odm.redis.call('hget', idxkey, value) == oldid then
-- remove the field from the instance hashtable so that
-- the next call to update_indices won't delete the index
- redis.call('hdel', idkey, field)
+ -- odm.redis.call('hdel', idkey, field)
table.insert(errors, 'Unique constraint "' .. field .. '" violated: "' .. value .. '" is already in database.')
end
end
@@ -311,10 +315,12 @@ odm.Model = {
end
end
if oper == 'delete' then
- local num = redis.call('del', idkey) + 0
+ local num = odm.redis.call('del', idkey) + 0
self:setrem(self.idset, id)
- for _, name in ipairs(self.meta.multifields) do
- redis.call('del', idkey .. ':' .. name)
+ if self.meta.multifields then
+ for _, name in ipairs(self.meta.multifields) do
+ odm.redis.call('del', idkey .. ':' .. name)
+ end
end
return num
else
@@ -357,6 +363,9 @@ function odm.model(meta)
return setmetatable(result, model_meta)
end
-- Return the module only when this module is not in REDIS
-if not (KEYS and ARGV) then
+if not redis then
return odm
-end
+else
+ odm.redis = redis
+end
+
View
141 stdnet/lib/lua/redis.lua
@@ -1,141 +0,0 @@
--- IMPORTANT!
--- THIS MODULE IS NOT USED AS A SCRIPT FOR REDIS
---
--- Mock Redis lua scripting for stand-alone lua scripts.
--- This module is useful for testing redis lua scripts outside redis
---
--- To use it
--- redis = require("redis")
--- redis.call(...
---
-local redis = {}
-local store = {}
-local bad_key = 'Redis Error: Operation performed against wrong type of key'
-
-local hash_meta = {name = 'hash'}
-local hash_list = {name = 'list'}
-
-local function is_hash(h)
- if type(h) == 'table' then
- return getmetatable(h).name == 'hash'
- else
- return false
- end
-end
-
-local function is_list(h)
- if type(h) == 'table' then
- return getmetatable(h).name == 'list'
- else
- return false
- end
-end
-
-local function encode (v)
- if v then
- return v .. ''
- else
- return ''
- end
-end
-
--- Utility for adding items to a list
-local function add_to_list (key, value, values, callback)
- local key = encode(key)
- local h = store[key]
- if not h then
- h = setmetatable({}, hash_list)
- store[key] = h
- end
- if is_list(h) then
- if callback then
- callback(h,encode(value))
- for _,v in ipairs(values) do
- callback(h,encode(v))
- end
- end
- return # h
- else
- error(bad_key)
- end
-end
-
--- REDIS COMMAND TABLE
-redis.commands = {
- --
- -- KEYS COMMAND
- exists = function(key)
- return store[encode(key)] ~= nil
- end,
- keys = function()
- local all = {}
- for name,_ in pairs(store) do
- table.insert(all, name)
- end
- return all
- end,
- --
- -- STRING COMMANDS
- get = function (key)
- local v = store[encode(key)]
- if type(v) == 'table' then
- error(bad_key)
- else
- return v
- end
- end,
- set = function (key, value)
- store[encode(key)] = value
- return {ok='OK'}
- end,
- --
- -- LIST COMMANDS
- llen = function(key)
- return add_to_list(key)
- end,
- lpush = function(key, value, ...)
- return add_to_list(key, value, arg, function(h,v) table.insert(h,1,v) end)
- end,
- rpush = function(key, value, ...)
- return add_to_list(key, value, arg, function(h,v) table.insert(h,v) end)
- end,
- --
- -- HASH COMMANDS
- hset = function(key, field, value)
- local key = encode(key)
- local h = store[key]
- if not h then
- h = setmetatable({}, hash_meta)
- store[key] = h
- end
- if is_hash(h) then
- r = h[field] and 1 or 0
- h[field] = value
- return r
- else
- error(bad_key)
- end
- end,
- hget = function(key, field)
- return store[key][field]
- end,
- hgetall = function(key)
- return store[key]
- end,
- --
- -- SERVER COMMANDS
- flushall = function ()
- store = {}
- end
-}
-
-redis.call = function (command, ...)
- local com = redis.commands[string.lower(command)]
- if com then
- return com(unpack(arg))
- else
- error('Unknown redis command ' .. command)
- end
-end
-
-return redis
View
68 tests/lua/odm.lua
@@ -1,13 +1,79 @@
-local t = require("tabletools")
+local tabletools = require("tabletools")
+local cjson = require("json")
local odm = require("odm")
+local redis = require("rserver")
+odm.redis = redis
+
+-- Connect to redis server
+redis.connect('fibpalap1d', 6379)
+redis.call('select', 15)
local suite = {}
+local model_meta = {
+ namespace = 'test:simplemodel',
+ id_name = 'id',
+ id_type = 1,
+ indices = {code=true, group=false}
+}
+
+local function commit_data (model, data)
+ local num, ctx, flat = # data, 0, {}
+ for _, sdata in ipairs(data) do
+ table.insert(flat, sdata.action or '')
+ table.insert(flat, sdata.id or '')
+ table.insert(flat, sdata.score or '')
+ local odata = sdata.data or {}
+ local count = 0
+ table.insert(flat, 0)
+ ctx = # flat
+ for field, value in pairs(odata) do
+ table.insert(flat, field)
+ table.insert(flat, value)
+ count = count + 1
+ end
+ flat[ctx] = 2 * count
+ end
+ return model:commit(# data, flat)
+end
+
suite.test_range_selectors = function ()
assert_true(odm.range_selectors['ge'](3, 2))
assert_true(odm.range_selectors['ge'](3, 3))
assert_false(odm.range_selectors['ge'](3, 4))
end
+suite.test_commit_simple = function ()
+ odm.redis.call('flushdb')
+ local model = odm.model(model_meta)
+ local ids = commit_data(model, {{action='add', data={code = 'bla'}},
+ {action='add', data={code = 'foo'}}})
+ assert_equal(# ids, 2)
+ assert_equal(ids[1][1], 1)
+ assert_equal(ids[1][2], 1)
+ assert_equal(ids[2][1], 2)
+ assert_equal(ids[2][2], 1)
+ --
+ assert_equal(redis.call('scard', model.idset), 2)
+ --
+ -- this should contain an error
+ local ids = commit_data(model, {{action='add', data={code = 'bla'}},
+ {action='add', data={code = 'pippo'}}})
+ assert_equal(# ids, 2)
+ assert_equal(ids[1][1], '')
+ assert_equal(ids[1][2], 0)
+ assert_equal(ids[2][1], 3)
+ assert_equal(ids[2][2], 1)
+ --
+ assert_equal(redis.call('scard', model.idset), 3)
+end
+
+suite.test_query = function ()
+ odm.redis.call('flushdb')
+ local model = odm.model(model_meta)
+ local ids = commit_data(model, {{action='add', data={code = 'bla'}},
+ {action='add', data={code = 'foo'}}})
+
+end
return suite
View
1,146 tests/lua/redis.lua
@@ -0,0 +1,1146 @@
+local redis = {
+ _VERSION = 'redis-lua 2.0.5-dev',
+ _DESCRIPTION = 'A Lua client library for the redis key value storage system.',
+ _COPYRIGHT = 'Copyright (C) 2009-2012 Daniele Alessandri',
+}
+
+-- The following line is used for backwards compatibility in order to keep the `Redis`
+-- global module name. Using `Redis` is now deprecated so you should explicitly assign
+-- the module to a local variable when requiring it: `local redis = require('redis')`.
+Redis = redis
+
+local unpack = _G.unpack or table.unpack
+local network, request, response = {}, {}, {}
+
+local defaults = {
+ host = '127.0.0.1',
+ port = 6379,
+ tcp_nodelay = true,
+ path = nil,
+}
+
+local function merge_defaults(parameters)
+ if parameters == nil then
+ parameters = {}
+ end
+ for k, v in pairs(defaults) do
+ if parameters[k] == nil then
+ parameters[k] = defaults[k]
+ end
+ end
+ return parameters
+end
+
+local function parse_boolean(v)
+ if v == '1' or v == 'true' or v == 'TRUE' then
+ return true
+ elseif v == '0' or v == 'false' or v == 'FALSE' then
+ return false
+ else
+ return nil
+ end
+end
+
+local function toboolean(value) return value == 1 end
+
+local function sort_request(client, command, key, params)
+ --[[ params = {
+ by = 'weight_*',
+ get = 'object_*',
+ limit = { 0, 10 },
+ sort = 'desc',
+ alpha = true,
+ } ]]
+ local query = { key }
+
+ if params then
+ if params.by then
+ table.insert(query, 'BY')
+ table.insert(query, params.by)
+ end
+
+ if type(params.limit) == 'table' then
+ -- TODO: check for lower and upper limits
+ table.insert(query, 'LIMIT')
+ table.insert(query, params.limit[1])
+ table.insert(query, params.limit[2])
+ end
+
+ if params.get then
+ if (type(params.get) == 'table') then
+ for _, getarg in pairs(params.get) do
+ table.insert(query, 'GET')
+ table.insert(query, getarg)
+ end
+ else
+ table.insert(query, 'GET')
+ table.insert(query, params.get)
+ end
+ end
+
+ if params.sort then
+ table.insert(query, params.sort)
+ end
+
+ if params.alpha == true then
+ table.insert(query, 'ALPHA')
+ end
+
+ if params.store then
+ table.insert(query, 'STORE')
+ table.insert(query, params.store)
+ end
+ end
+
+ request.multibulk(client, command, query)
+end
+
+local function zset_range_request(client, command, ...)
+ local args, opts = {...}, { }
+
+ if #args >= 1 and type(args[#args]) == 'table' then
+ local options = table.remove(args, #args)
+ if options.withscores then
+ table.insert(opts, 'WITHSCORES')
+ end
+ end
+
+ for _, v in pairs(opts) do table.insert(args, v) end
+ request.multibulk(client, command, args)
+end
+
+local function zset_range_byscore_request(client, command, ...)
+ local args, opts = {...}, { }
+
+ if #args >= 1 and type(args[#args]) == 'table' then
+ local options = table.remove(args, #args)
+ if options.limit then
+ table.insert(opts, 'LIMIT')
+ table.insert(opts, options.limit.offset or options.limit[1])
+ table.insert(opts, options.limit.count or options.limit[2])
+ end
+ if options.withscores then
+ table.insert(opts, 'WITHSCORES')
+ end
+ end
+
+ for _, v in pairs(opts) do table.insert(args, v) end
+ request.multibulk(client, command, args)
+end
+
+local function zset_range_reply(reply, command, ...)
+ local args = {...}
+ local opts = args[4]
+ if opts and (opts.withscores or string.lower(tostring(opts)) == 'withscores') then
+ local new_reply = { }
+ for i = 1, #reply, 2 do
+ table.insert(new_reply, { reply[i], reply[i + 1] })
+ end
+ return new_reply
+ else
+ return reply
+ end
+end
+
+local function zset_store_request(client, command, ...)
+ local args, opts = {...}, { }
+
+ if #args >= 1 and type(args[#args]) == 'table' then
+ local options = table.remove(args, #args)
+ if options.weights and type(options.weights) == 'table' then
+ table.insert(opts, 'WEIGHTS')
+ for _, weight in ipairs(options.weights) do
+ table.insert(opts, weight)
+ end
+ end
+ if options.aggregate then
+ table.insert(opts, 'AGGREGATE')
+ table.insert(opts, options.aggregate)
+ end
+ end
+
+ for _, v in pairs(opts) do table.insert(args, v) end
+ request.multibulk(client, command, args)
+end
+
+local function mset_filter_args(client, command, ...)
+ local args, arguments = {...}, {}
+ if (#args == 1 and type(args[1]) == 'table') then
+ for k,v in pairs(args[1]) do
+ table.insert(arguments, k)
+ table.insert(arguments, v)
+ end
+ else
+ arguments = args
+ end
+ request.multibulk(client, command, arguments)
+end
+
+local function hash_multi_request_builder(builder_callback)
+ return function(client, command, ...)
+ local args, arguments = {...}, { }
+ if #args == 2 then
+ table.insert(arguments, args[1])
+ for k, v in pairs(args[2]) do
+ builder_callback(arguments, k, v)
+ end
+ else
+ arguments = args
+ end
+ request.multibulk(client, command, arguments)
+ end
+end
+
+local function parse_info(response)
+ local info = {}
+ local current = info
+
+ response:gsub('([^\r\n]*)\r\n', function(kv)
+ if kv == '' then return end
+
+ local section = kv:match('^# (%w+)$')
+ if section then
+ current = {}
+ info[section:lower()] = current
+ return
+ end
+
+ local k,v = kv:match(('([^:]*):([^:]*)'):rep(1))
+ if k:match('db%d+') then
+ current[k] = {}
+ v:gsub(',', function(dbkv)
+ local dbk,dbv = kv:match('([^:]*)=([^:]*)')
+ current[k][dbk] = dbv
+ end)
+ else
+ current[k] = v
+ end
+ end)
+
+ return info
+end
+
+local function load_methods(proto, commands)
+ local client = setmetatable ({}, getmetatable(proto))
+
+ for cmd, fn in pairs(commands) do
+ if type(fn) ~= 'function' then
+ redis.error('invalid type for command ' .. cmd .. '(must be a function)')
+ end
+ client[cmd] = fn
+ end
+
+ for i, v in pairs(proto) do
+ client[i] = v
+ end
+
+ return client
+end
+
+local function create_client(proto, client_socket, commands)
+ local client = load_methods(proto, commands)
+ client.error = redis.error
+ client.network = {
+ socket = client_socket,
+ read = network.read,
+ write = network.write,
+ }
+ client.requests = {
+ multibulk = request.multibulk,
+ }
+ return client
+end
+
+-- ############################################################################
+
+function network.write(client, buffer)
+ local _, err = client.network.socket:send(buffer)
+ if err then client.error(err) end
+end
+
+function network.read(client, len)
+ if len == nil then len = '*l' end
+ local line, err = client.network.socket:receive(len)
+ if not err then return line else client.error('connection error: ' .. err) end
+end
+
+-- ############################################################################
+
+function response.read(client)
+ local payload = client.network.read(client)
+ local prefix, data = payload:sub(1, -#payload), payload:sub(2)
+
+ -- status reply
+ if prefix == '+' then
+ if data == 'OK' then
+ return true
+ elseif data == 'QUEUED' then
+ return { queued = true }
+ else
+ return data
+ end
+
+ -- error reply
+ elseif prefix == '-' then
+ return client.error('redis error: ' .. data)
+
+ -- integer reply
+ elseif prefix == ':' then
+ local number = tonumber(data)
+
+ if not number then
+ if res == 'nil' then
+ return nil
+ end
+ client.error('cannot parse '..res..' as a numeric response.')
+ end
+
+ return number
+
+ -- bulk reply
+ elseif prefix == '$' then
+ local length = tonumber(data)
+
+ if not length then
+ client.error('cannot parse ' .. length .. ' as data length')
+ end
+
+ if length == -1 then
+ return nil
+ end
+
+ local nextchunk = client.network.read(client, length + 2)
+
+ return nextchunk:sub(1, -3)
+
+ -- multibulk reply
+ elseif prefix == '*' then
+ local count = tonumber(data)
+
+ if count == -1 then
+ return nil
+ end
+
+ local list = {}
+ if count > 0 then
+ local reader = response.read
+ for i = 1, count do
+ list[i] = reader(client)
+ end
+ end
+ return list
+
+ -- unknown type of reply
+ else
+ return client.error('unknown response prefix: ' .. prefix)
+ end
+end
+
+-- ############################################################################
+
+function request.raw(client, buffer)
+ local bufferType = type(buffer)
+
+ if bufferType == 'table' then
+ client.network.write(client, table.concat(buffer))
+ elseif bufferType == 'string' then
+ client.network.write(client, buffer)
+ else
+ client.error('argument error: ' .. bufferType)
+ end
+end
+
+function request.multibulk(client, command, ...)
+ local args = {...}
+ local argsn = #args
+ local buffer = { true, true }
+
+ if argsn == 1 and type(args[1]) == 'table' then
+ argsn, args = #args[1], args[1]
+ end
+
+ buffer[1] = '*' .. tostring(argsn + 1) .. "\r\n"
+ buffer[2] = '$' .. #command .. "\r\n" .. command .. "\r\n"
+
+ local table_insert = table.insert
+ for _, argument in pairs(args) do
+ local s_argument = tostring(argument)
+ table_insert(buffer, '$' .. #s_argument .. "\r\n" .. s_argument .. "\r\n")
+ end
+
+ client.network.write(client, table.concat(buffer))
+end
+
+-- ############################################################################
+
+local function custom(command, send, parse)
+ command = string.upper(command)
+ return function(client, ...)
+ send(client, command, ...)
+ local reply = response.read(client)
+
+ if type(reply) == 'table' and reply.queued then
+ reply.parser = parse
+ return reply
+ else
+ if parse then
+ return parse(reply, command, ...)
+ end
+ return reply
+ end
+ end
+end
+
+local function command(command, opts)
+ if opts == nil or type(opts) == 'function' then
+ return custom(command, request.multibulk, opts)
+ else
+ return custom(command, opts.request or request.multibulk, opts.response)
+ end
+end
+
+local define_command_impl = function(target, name, opts)
+ local opts = opts or {}
+ target[string.lower(name)] = custom(
+ opts.command or string.upper(name),
+ opts.request or request.multibulk,
+ opts.response or nil
+ )
+end
+
+local undefine_command_impl = function(target, name)
+ target[string.lower(name)] = nil
+end
+
+-- ############################################################################
+
+local client_prototype = {}
+
+client_prototype.raw_cmd = function(client, buffer)
+ request.raw(client, buffer .. "\r\n")
+ return response.read(client)
+end
+
+-- obsolete
+client_prototype.define_command = function(client, name, opts)
+ define_command_impl(client, name, opts)
+end
+
+-- obsolete
+client_prototype.undefine_command = function(client, name)
+ undefine_command_impl(client, name)
+end
+
+client_prototype.quit = function(client)
+ request.multibulk(client, 'QUIT')
+ client.network.socket:shutdown()
+ return true
+end
+
+client_prototype.shutdown = function(client)
+ request.multibulk(client, 'SHUTDOWN')
+ client.network.socket:shutdown()
+end
+
+-- Command pipelining
+
+client_prototype.pipeline = function(client, block)
+ local requests, replies, parsers = {}, {}, {}
+ local table_insert = table.insert
+ local socket_write, socket_read = client.network.write, client.network.read
+
+ client.network.write = function(_, buffer)
+ table_insert(requests, buffer)
+ end
+
+ -- TODO: this hack is necessary to temporarily reuse the current
+ -- request -> response handling implementation of redis-lua
+ -- without further changes in the code, but it will surely
+ -- disappear when the new command-definition infrastructure
+ -- will finally be in place.
+ client.network.read = function() return '+QUEUED' end
+
+ local pipeline = setmetatable({}, {
+ __index = function(env, name)
+ local cmd = client[name]
+ if not cmd then
+ client.error('unknown redis command: ' .. name, 2)
+ end
+ return function(self, ...)
+ local reply = cmd(client, ...)
+ table_insert(parsers, #requests, reply.parser)
+ return reply
+ end
+ end
+ })
+
+ local success, retval = pcall(block, pipeline)
+
+ client.network.write, client.network.read = socket_write, socket_read
+ if not success then client.error(retval, 0) end
+
+ client.network.write(client, table.concat(requests, ''))
+
+ for i = 1, #requests do
+ local reply, parser = response.read(client), parsers[i]
+ if parser then
+ reply = parser(reply)
+ end
+ table_insert(replies, i, reply)
+ end
+
+ return replies, #requests
+end
+
+-- Publish/Subscribe
+
+do
+ local channels = function(channels)
+ if type(channels) == 'string' then
+ channels = { channels }
+ end
+ return channels
+ end
+
+ local subscribe = function(client, ...)
+ request.multibulk(client, 'subscribe', ...)
+ end
+ local psubscribe = function(client, ...)
+ request.multibulk(client, 'psubscribe', ...)
+ end
+ local unsubscribe = function(client, ...)
+ request.multibulk(client, 'unsubscribe')
+ end
+ local punsubscribe = function(client, ...)
+ request.multibulk(client, 'punsubscribe')
+ end
+
+ local consumer_loop = function(client)
+ local aborting, subscriptions = false, 0
+
+ local abort = function()
+ if not aborting then
+ unsubscribe(client)
+ punsubscribe(client)
+ aborting = true
+ end
+ end
+
+ return coroutine.wrap(function()
+ while true do
+ local message
+ local response = response.read(client)
+
+ if response[1] == 'pmessage' then
+ message = {
+ kind = response[1],
+ pattern = response[2],
+ channel = response[3],
+ payload = response[4],
+ }
+ else
+ message = {
+ kind = response[1],
+ channel = response[2],
+ payload = response[3],
+ }
+ end
+
+ if string.match(message.kind, '^p?subscribe$') then
+ subscriptions = subscriptions + 1
+ end
+ if string.match(message.kind, '^p?unsubscribe$') then
+ subscriptions = subscriptions - 1
+ end
+
+ if aborting and subscriptions == 0 then
+ break
+ end
+ coroutine.yield(message, abort)
+ end
+ end)
+ end
+
+ client_prototype.pubsub = function(client, subscriptions)
+ if type(subscriptions) == 'table' then
+ if subscriptions.subscribe then
+ subscribe(client, channels(subscriptions.subscribe))
+ end
+ if subscriptions.psubscribe then
+ psubscribe(client, channels(subscriptions.psubscribe))
+ end
+ end
+ return consumer_loop(client)
+ end
+end
+
+-- Redis transactions (MULTI/EXEC)
+
+do
+ local function identity(...) return ... end
+ local emptytable = {}
+
+ local function initialize_transaction(client, options, block, queued_parsers)
+ local table_insert = table.insert
+ local coro = coroutine.create(block)
+
+ if options.watch then
+ local watch_keys = {}
+ for _, key in pairs(options.watch) do
+ table_insert(watch_keys, key)
+ end
+ if #watch_keys > 0 then
+ client:watch(unpack(watch_keys))
+ end
+ end
+
+ local transaction_client = setmetatable({}, {__index=client})
+ transaction_client.exec = function(...)
+ client.error('cannot use EXEC inside a transaction block')
+ end
+ transaction_client.multi = function(...)
+ coroutine.yield()
+ end
+ transaction_client.commands_queued = function()
+ return #queued_parsers
+ end
+
+ assert(coroutine.resume(coro, transaction_client))
+
+ transaction_client.multi = nil
+ transaction_client.discard = function(...)
+ local reply = client:discard()
+ for i, v in pairs(queued_parsers) do
+ queued_parsers[i]=nil
+ end
+ coro = initialize_transaction(client, options, block, queued_parsers)
+ return reply
+ end
+ transaction_client.watch = function(...)
+ client.error('WATCH inside MULTI is not allowed')
+ end
+ setmetatable(transaction_client, { __index = function(t, k)
+ local cmd = client[k]
+ if type(cmd) == "function" then
+ local function queuey(self, ...)
+ local reply = cmd(client, ...)
+ assert((reply or emptytable).queued == true, 'a QUEUED reply was expected')
+ table_insert(queued_parsers, reply.parser or identity)
+ return reply
+ end
+ t[k]=queuey
+ return queuey
+ else
+ return cmd
+ end
+ end
+ })
+ client:multi()
+ return coro
+ end
+
+ local function transaction(client, options, coroutine_block, attempts)
+ local queued_parsers, replies = {}, {}
+ local retry = tonumber(attempts) or tonumber(options.retry) or 2
+ local coro = initialize_transaction(client, options, coroutine_block, queued_parsers)
+
+ local success, retval
+ if coroutine.status(coro) == 'suspended' then
+ success, retval = coroutine.resume(coro)
+ else
+ -- do not fail if the coroutine has not been resumed (missing t:multi() with CAS)
+ success, retval = true, 'empty transaction'
+ end
+ if #queued_parsers == 0 or not success then
+ client:discard()
+ assert(success, retval)
+ return replies, 0
+ end
+
+ local raw_replies = client:exec()
+ if not raw_replies then
+ if (retry or 0) <= 0 then
+ client.error("MULTI/EXEC transaction aborted by the server")
+ else
+ --we're not quite done yet
+ return transaction(client, options, coroutine_block, retry - 1)
+ end
+ end
+
+ local table_insert = table.insert
+ for i, parser in pairs(queued_parsers) do
+ table_insert(replies, i, parser(raw_replies[i]))
+ end
+
+ return replies, #queued_parsers
+ end
+
+ client_prototype.transaction = function(client, arg1, arg2)
+ local options, block
+ if not arg2 then
+ options, block = {}, arg1
+ elseif arg1 then --and arg2, implicitly
+ options, block = type(arg1)=="table" and arg1 or { arg1 }, arg2
+ else
+ client.error("Invalid parameters for redis transaction.")
+ end
+
+ if not options.watch then
+ watch_keys = { }
+ for i, v in pairs(options) do
+ if tonumber(i) then
+ table.insert(watch_keys, v)
+ options[i] = nil
+ end
+ end
+ options.watch = watch_keys
+ elseif not (type(options.watch) == 'table') then
+ options.watch = { options.watch }
+ end
+
+ if not options.cas then
+ local tx_block = block
+ block = function(client, ...)
+ client:multi()
+ return tx_block(client, ...) --can't wrap this in pcall because we're in a coroutine.
+ end
+ end
+
+ return transaction(client, options, block)
+ end
+end
+
+-- MONITOR context
+
+do
+ local monitor_loop = function(client)
+ local monitoring = true
+
+ -- Tricky since the payload format changed starting from Redis 2.6.
+ local pattern = '^(%d+%.%d+)( ?.- ?) ?"(%a+)" ?(.-)$'
+
+ local abort = function()
+ monitoring = false
+ end
+
+ return coroutine.wrap(function()
+ client:monitor()
+
+ while monitoring do
+ local message, matched
+ local response = response.read(client)
+
+ local ok = response:gsub(pattern, function(time, info, cmd, args)
+ message = {
+ timestamp = tonumber(time),
+ client = info:match('%d+.%d+.%d+.%d+:%d+'),
+ database = tonumber(info:match('%d+')) or 0,
+ command = cmd,
+ arguments = args:match('.+'),
+ }
+ matched = true
+ end)
+
+ if not matched then
+ client.error('Unable to match MONITOR payload: '..response)
+ end
+
+ coroutine.yield(message, abort)
+ end
+ end)
+ end
+
+ client_prototype.monitor_messages = function(client)
+ return monitor_loop(client)
+ end
+end
+
+-- ############################################################################
+
+local function connect_tcp(socket, parameters)
+ local host, port = parameters.host, tonumber(parameters.port)
+ if parameters.timeout then
+ socket:settimeout(parameters.timeout, 't')
+ end
+
+ local ok, err = socket:connect(host, port)
+ if not ok then
+ redis.error('could not connect to '..host..':'..port..' ['..err..']')
+ end
+ socket:setoption('tcp-nodelay', parameters.tcp_nodelay)
+ return socket
+end
+
+local function connect_unix(socket, parameters)
+ local ok, err = socket:connect(parameters.path)
+ if not ok then
+ redis.error('could not connect to '..parameters.path..' ['..err..']')
+ end
+ return socket
+end
+
+local function create_connection(parameters)
+ if parameters.socket then
+ return parameters.socket
+ end
+
+ local perform_connection, socket
+
+ if parameters.scheme == 'unix' then
+ perform_connection, socket = connect_unix, require('socket.unix')
+ assert(socket, 'your build of LuaSocket does not support UNIX domain sockets')
+ else
+ if parameters.scheme then
+ local scheme = parameters.scheme
+ assert(scheme == 'redis' or scheme == 'tcp', 'invalid scheme: '..scheme)
+ end
+ perform_connection, socket = connect_tcp, require('socket').tcp
+ end
+
+ return perform_connection(socket(), parameters)
+end
+
+-- ############################################################################
+
+function redis.error(message, level)
+ error(message, (level or 1) + 1)
+end
+
+function redis.connect(...)
+ local args, parameters = {...}, nil
+
+ if #args == 1 then
+ if type(args[1]) == 'table' then
+ parameters = args[1]
+ else
+ local uri = require('socket.url')
+ parameters = uri.parse(select(1, ...))
+ if parameters.scheme then
+ if parameters.query then
+ for k, v in parameters.query:gmatch('([-_%w]+)=([-_%w]+)') do
+ if k == 'tcp_nodelay' or k == 'tcp-nodelay' then
+ parameters.tcp_nodelay = parse_boolean(v)
+ elseif k == 'timeout' then
+ parameters.timeout = tonumber(v)
+ end
+ end
+ end
+ else
+ parameters.host = parameters.path
+ end
+ end
+ elseif #args > 1 then
+ local host, port, timeout = unpack(args)
+ parameters = { host = host, port = port, timeout = tonumber(timeout) }
+ end
+
+ local commands = redis.commands or {}
+ if type(commands) ~= 'table' then
+ redis.error('invalid type for the commands table')
+ end
+
+ local socket = create_connection(merge_defaults(parameters))
+ local client = create_client(client_prototype, socket, commands)
+
+ return client
+end
+
+function redis.command(cmd, opts)
+ return command(cmd, opts)
+end
+
+-- obsolete
+function redis.define_command(name, opts)
+ define_command_impl(redis.commands, name, opts)
+end
+
+-- obsolete
+function redis.undefine_command(name)
+ undefine_command_impl(redis.commands, name)
+end
+
+-- ############################################################################
+
+-- Commands defined in this table do not take the precedence over
+-- methods defined in the client prototype table.
+
+redis.commands = {
+ -- commands operating on the key space
+ exists = command('EXISTS', {
+ response = toboolean
+ }),
+ del = command('DEL'),
+ type = command('TYPE'),
+ rename = command('RENAME'),
+ renamenx = command('RENAMENX', {
+ response = toboolean
+ }),
+ expire = command('EXPIRE', {
+ response = toboolean
+ }),
+ pexpire = command('PEXPIRE', { -- >= 2.6
+ response = toboolean
+ }),
+ expireat = command('EXPIREAT', {
+ response = toboolean
+ }),
+ pexpireat = command('PEXPIREAT', { -- >= 2.6
+ response = toboolean
+ }),
+ ttl = command('TTL'),
+ pttl = command('PTTL'), -- >= 2.6
+ move = command('MOVE', {
+ response = toboolean
+ }),
+ dbsize = command('DBSIZE'),
+ persist = command('PERSIST', { -- >= 2.2
+ response = toboolean
+ }),
+ keys = command('KEYS', {
+ response = function(response)
+ if type(response) == 'string' then
+ -- backwards compatibility path for Redis < 2.0
+ local keys = {}
+ response:gsub('[^%s]+', function(key)
+ table.insert(keys, key)
+ end)
+ response = keys
+ end
+ return response
+ end
+ }),
+ randomkey = command('RANDOMKEY', {
+ response = function(response)
+ if response == '' then
+ return nil
+ else
+ return response
+ end
+ end
+ }),
+ sort = command('SORT', {
+ request = sort_request,
+ }),
+
+ -- commands operating on string values
+ set = command('SET'),
+ setnx = command('SETNX', {
+ response = toboolean
+ }),
+ setex = command('SETEX'), -- >= 2.0
+ psetex = command('PSETEX'), -- >= 2.6
+ mset = command('MSET', {
+ request = mset_filter_args
+ }),
+ msetnx = command('MSETNX', {
+ request = mset_filter_args,
+ response = toboolean
+ }),
+ get = command('GET'),
+ mget = command('MGET'),
+ getset = command('GETSET'),
+ incr = command('INCR'),
+ incrby = command('INCRBY'),
+ incrbyfloat = command('INCRBYFLOAT', { -- >= 2.6
+ response = function(reply, command, ...)
+ return tonumber(reply)
+ end,
+ }),
+ decr = command('DECR'),
+ decrby = command('DECRBY'),
+ append = command('APPEND'), -- >= 2.0
+ substr = command('SUBSTR'), -- >= 2.0
+ strlen = command('STRLEN'), -- >= 2.2
+ setrange = command('SETRANGE'), -- >= 2.2
+ getrange = command('GETRANGE'), -- >= 2.2
+ setbit = command('SETBIT'), -- >= 2.2
+ getbit = command('GETBIT'), -- >= 2.2
+
+ -- commands operating on lists
+ rpush = command('RPUSH'),
+ lpush = command('LPUSH'),
+ llen = command('LLEN'),
+ lrange = command('LRANGE'),
+ ltrim = command('LTRIM'),
+ lindex = command('LINDEX'),
+ lset = command('LSET'),
+ lrem = command('LREM'),
+ lpop = command('LPOP'),
+ rpop = command('RPOP'),
+ rpoplpush = command('RPOPLPUSH'),
+ blpop = command('BLPOP'), -- >= 2.0
+ brpop = command('BRPOP'), -- >= 2.0
+ rpushx = command('RPUSHX'), -- >= 2.2
+ lpushx = command('LPUSHX'), -- >= 2.2
+ linsert = command('LINSERT'), -- >= 2.2
+ brpoplpush = command('BRPOPLPUSH'), -- >= 2.2
+
+ -- commands operating on sets
+ sadd = command('SADD'),
+ srem = command('SREM'),
+ spop = command('SPOP'),
+ smove = command('SMOVE', {
+ response = toboolean
+ }),
+ scard = command('SCARD'),
+ sismember = command('SISMEMBER', {
+ response = toboolean
+ }),
+ sinter = command('SINTER'),
+ sinterstore = command('SINTERSTORE'),
+ sunion = command('SUNION'),
+ sunionstore = command('SUNIONSTORE'),
+ sdiff = command('SDIFF'),
+ sdiffstore = command('SDIFFSTORE'),
+ smembers = command('SMEMBERS'),
+ srandmember = command('SRANDMEMBER'),
+
+ -- commands operating on sorted sets
+ zadd = command('ZADD'),
+ zincrby = command('ZINCRBY'),
+ zrem = command('ZREM'),
+ zrange = command('ZRANGE', {
+ request = zset_range_request,
+ response = zset_range_reply,
+ }),
+ zrevrange = command('ZREVRANGE', {
+ request = zset_range_request,
+ response = zset_range_reply,
+ }),
+ zrangebyscore = command('ZRANGEBYSCORE', {
+ request = zset_range_byscore_request,
+ response = zset_range_reply,
+ }),
+ zrevrangebyscore = command('ZREVRANGEBYSCORE', { -- >= 2.2
+ request = zset_range_byscore_request,
+ response = zset_range_reply,
+ }),
+ zunionstore = command('ZUNIONSTORE', { -- >= 2.0
+ request = zset_store_request
+ }),
+ zinterstore = command('ZINTERSTORE', { -- >= 2.0
+ request = zset_store_request
+ }),
+ zcount = command('ZCOUNT'),
+ zcard = command('ZCARD'),
+ zscore = command('ZSCORE'),
+ zremrangebyscore = command('ZREMRANGEBYSCORE'),
+ zrank = command('ZRANK'), -- >= 2.0
+ zrevrank = command('ZREVRANK'), -- >= 2.0
+ zremrangebyrank = command('ZREMRANGEBYRANK'), -- >= 2.0
+
+ -- commands operating on hashes
+ hset = command('HSET', { -- >= 2.0
+ response = toboolean
+ }),
+ hsetnx = command('HSETNX', { -- >= 2.0
+ response = toboolean
+ }),
+ hmset = command('HMSET', { -- >= 2.0
+ request = hash_multi_request_builder(function(args, k, v)
+ table.insert(args, k)
+ table.insert(args, v)
+ end),
+ }),
+ hincrby = command('HINCRBY'), -- >= 2.0
+ hincrbyfloat = command('HINCRBYFLOAT', {-- >= 2.6
+ response = function(reply, command, ...)
+ return tonumber(reply)
+ end,
+ }),
+ hget = command('HGET'), -- >= 2.0
+ hmget = command('HMGET', { -- >= 2.0
+ request = hash_multi_request_builder(function(args, k, v)
+ table.insert(args, v)
+ end),
+ }),
+ hdel = command('HDEL'), -- >= 2.0
+ hexists = command('HEXISTS', { -- >= 2.0
+ response = toboolean
+ }),
+ hlen = command('HLEN'), -- >= 2.0
+ hkeys = command('HKEYS'), -- >= 2.0
+ hvals = command('HVALS'), -- >= 2.0
+ hgetall = command('HGETALL', { -- >= 2.0
+ response = function(reply, command, ...)
+ local new_reply = { }
+ for i = 1, #reply, 2 do new_reply[reply[i]] = reply[i + 1] end
+ return new_reply
+ end
+ }),
+
+ -- connection related commands
+ ping = command('PING', {
+ response = function(response) return response == 'PONG' end
+ }),
+ echo = command('ECHO'),
+ auth = command('AUTH'),
+ select = command('SELECT'),
+
+ -- transactions
+ multi = command('MULTI'), -- >= 2.0
+ exec = command('EXEC'), -- >= 2.0
+ discard = command('DISCARD'), -- >= 2.0
+ watch = command('WATCH'), -- >= 2.2
+ unwatch = command('UNWATCH'), -- >= 2.2
+
+ -- publish - subscribe
+ subscribe = command('SUBSCRIBE'), -- >= 2.0
+ unsubscribe = command('UNSUBSCRIBE'), -- >= 2.0
+ psubscribe = command('PSUBSCRIBE'), -- >= 2.0
+ punsubscribe = command('PUNSUBSCRIBE'), -- >= 2.0
+ publish = command('PUBLISH'), -- >= 2.0
+
+ -- redis scripting
+ eval = command('EVAL'), -- >= 2.6
+ evalsha = command('EVALSHA'), -- >= 2.6
+ script = command('SCRIPT'), -- >= 2.6
+
+ -- remote server control commands
+ bgrewriteaof = command('BGREWRITEAOF'),
+ config = command('CONFIG', { -- >= 2.0
+ response = function(reply, command, ...)
+ if (type(reply) == 'table') then
+ local new_reply = { }
+ for i = 1, #reply, 2 do new_reply[reply[i]] = reply[i + 1] end
+ return new_reply
+ end
+
+ return reply
+ end
+ }),
+ client = command('CLIENT'), -- >= 2.4
+ slaveof = command('SLAVEOF'),
+ save = command('SAVE'),
+ bgsave = command('BGSAVE'),
+ lastsave = command('LASTSAVE'),
+ flushdb = command('FLUSHDB'),
+ flushall = command('FLUSHALL'),
+ monitor = command('MONITOR'),
+ time = command('TIME'), -- >= 2.6
+ slowlog = command('SLOWLOG', { -- >= 2.2.13
+ response = function(reply, command, ...)
+ if (type(reply) == 'table') then
+ local structured = { }
+ for index, entry in ipairs(reply) do
+ structured[index] = {
+ id = tonumber(entry[1]),
+ timestamp = tonumber(entry[2]),
+ duration = tonumber(entry[3]),
+ command = entry[4],
+ }
+ end
+ return structured
+ end
+
+ return reply
+ end
+ }),
+ info = command('INFO', {
+ response = parse_info,
+ }),
+}
+
+-- ############################################################################
+
+return redis
View
33 tests/lua/redis_mock.lua
@@ -1,33 +0,0 @@
-local redis = require("redis")
-
-local suite = {}
-
-local function clear ()
- redis.call('flushall')
- assert_true(# redis.call('keys') == 0)
-end
-
-suite.test_flushall = function()
- clear()
-end
-
-suite.test_set_get_exists = function()
- clear()
- assert_false(redis.call('exists','foo'))
- assert_equal(redis.call('set','foo','bla')['ok'],'OK')
- assert_true(redis.call('exists','foo'))
- assert_false(redis.call('exists','fooo'))
- assert_equal(redis.call('get','foo'), 'bla')
-end
-
-
-suite.test_list = function()
- clear()
- assert_false(redis.call('exists','foo'))
- assert_equal(1, redis.call('lpush','foo','ciao'))
- assert_equal(2, redis.call('rpush','foo','luca'))
- assert_equal(2, redis.call('llen', 'foo'))
-end
-
-
-return suite
View
32 tests/lua/rserver.lua
@@ -0,0 +1,32 @@
+-- Obtain the redis client
+local client = require("redis")
+local redis = {
+ hsetnx = function (result)
+ if result then
+ return 1
+ else
+ return 0
+ end
+ end
+}
+
+redis.connect = function (...)
+ redis.client = client.connect(unpack(arg))
+end
+
+redis.call = function (command, ...)
+ local lcom = string.lower(command)
+ local com = redis.client[lcom]
+ if com then
+ local callback = redis[lcom]
+ local res = com(redis.client, unpack(arg))
+ if callback then
+ res = callback(res)
+ end
+ return res
+ else
+ error('Unknown redis command ' .. command)
+ end
+end
+
+return redis
Please sign in to comment.
Something went wrong with that request. Please try again.