mirror of
https://github.com/fluencelabs/redis
synced 2025-03-19 09:00:51 +00:00
Lua client updated
This commit is contained in:
parent
8b382972b0
commit
928394cd12
@ -28,5 +28,6 @@ http://code.google.com/p/phpredis/
|
||||
|
||||
Lua lib source code:
|
||||
http://github.com/nrk/redis-lua/tree/master
|
||||
git://github.com/nrk/redis-lua.git
|
||||
|
||||
For all the rest check the Redis tarball or Git repository.
|
||||
|
@ -1,105 +1,60 @@
|
||||
module('Redis', package.seeall)
|
||||
local _G = _G
|
||||
local require, error, type, print = require, error, type, print
|
||||
local table, pairs, tostring, tonumber = table, pairs, tostring, tonumber
|
||||
|
||||
require('socket') -- requires LuaSocket as a dependency
|
||||
module('Redis')
|
||||
|
||||
-- ############################################################################
|
||||
local socket = require('socket') -- requires LuaSocket as a dependency
|
||||
|
||||
local protocol = {
|
||||
newline = '\r\n', ok = 'OK', err = 'ERR', null = 'nil',
|
||||
}
|
||||
local redis_commands = {}
|
||||
local network, request, response, utils = {}, {}, {}, {}, {}
|
||||
|
||||
-- ############################################################################
|
||||
local protocol = { newline = '\r\n', ok = 'OK', err = 'ERR', null = 'nil' }
|
||||
|
||||
local function toboolean(value)
|
||||
return value == 1
|
||||
local function toboolean(value) return value == 1 end
|
||||
|
||||
local function load_methods(proto, methods)
|
||||
local redis = _G.setmetatable ({}, _G.getmetatable(proto))
|
||||
for i, v in pairs(proto) do redis[i] = v end
|
||||
|
||||
for i, v in pairs(methods) do redis[i] = v end
|
||||
return redis
|
||||
end
|
||||
|
||||
local function _write(self, buffer)
|
||||
local _, err = self.socket:send(buffer)
|
||||
-- ############################################################################
|
||||
|
||||
function network.write(client, buffer)
|
||||
local _, err = client.socket:send(buffer)
|
||||
if err then error(err) end
|
||||
end
|
||||
|
||||
local function _read(self, len)
|
||||
function network.read(client, len)
|
||||
if len == nil then len = '*l' end
|
||||
local line, err = self.socket:receive(len)
|
||||
local line, err = client.socket:receive(len)
|
||||
if not err then return line else error('Connection error: ' .. err) end
|
||||
end
|
||||
|
||||
-- ############################################################################
|
||||
|
||||
local function _read_response(self)
|
||||
if options and options.close == true then return end
|
||||
|
||||
local res = _read(self)
|
||||
function response.read(client)
|
||||
local res = network.read(client)
|
||||
local prefix = res:sub(1, -#res)
|
||||
local response_handler = protocol.prefixes[prefix]
|
||||
|
||||
if not response_handler then
|
||||
error("Unknown response prefix: " .. prefix)
|
||||
else
|
||||
return response_handler(self, res)
|
||||
return response_handler(client, res)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function _send_raw(self, buffer)
|
||||
-- TODO: optimize
|
||||
local bufferType = type(buffer)
|
||||
|
||||
if bufferType == 'string' then
|
||||
_write(self, buffer)
|
||||
elseif bufferType == 'table' then
|
||||
_write(self, table.concat(buffer))
|
||||
else
|
||||
error('Argument error: ' .. bufferType)
|
||||
end
|
||||
|
||||
return _read_response(self)
|
||||
function response.status(client, data)
|
||||
local sub = data:sub(2)
|
||||
if sub == protocol.ok then return true else return sub end
|
||||
end
|
||||
|
||||
local function _send_inline(self, command, ...)
|
||||
if arg.n == 0 then
|
||||
_write(self, command .. protocol.newline)
|
||||
else
|
||||
local arguments = arg
|
||||
arguments.n = nil
|
||||
|
||||
if #arguments > 0 then
|
||||
arguments = table.concat(arguments, ' ')
|
||||
else
|
||||
arguments = ''
|
||||
end
|
||||
|
||||
_write(self, command .. ' ' .. arguments .. protocol.newline)
|
||||
end
|
||||
|
||||
return _read_response(self)
|
||||
end
|
||||
|
||||
local function _send_bulk(self, command, ...)
|
||||
local arguments = arg
|
||||
local data = tostring(table.remove(arguments))
|
||||
arguments.n = nil
|
||||
|
||||
-- TODO: optimize
|
||||
if #arguments > 0 then
|
||||
arguments = table.concat(arguments, ' ')
|
||||
else
|
||||
arguments = ''
|
||||
end
|
||||
|
||||
return _send_raw(self, {
|
||||
command, ' ', arguments, ' ', #data, protocol.newline, data, protocol.newline
|
||||
})
|
||||
end
|
||||
|
||||
|
||||
local function _read_line(self, response)
|
||||
return response:sub(2)
|
||||
end
|
||||
|
||||
local function _read_error(self, response)
|
||||
local err_line = response:sub(2)
|
||||
function response.error(client, data)
|
||||
local err_line = data:sub(2)
|
||||
|
||||
if err_line:sub(1, 3) == protocol.err then
|
||||
error("Redis error: " .. err_line:sub(5))
|
||||
@ -108,21 +63,21 @@ local function _read_error(self, response)
|
||||
end
|
||||
end
|
||||
|
||||
local function _read_bulk(self, response)
|
||||
local str = response:sub(2)
|
||||
function response.bulk(client, data)
|
||||
local str = data:sub(2)
|
||||
local len = tonumber(str)
|
||||
|
||||
if not len then
|
||||
error('Cannot parse ' .. str .. ' as data length.')
|
||||
else
|
||||
if len == -1 then return nil end
|
||||
local data = _read(self, len + 2)
|
||||
return data:sub(1, -3);
|
||||
local next_chunk = network.read(client, len + 2)
|
||||
return next_chunk:sub(1, -3);
|
||||
end
|
||||
end
|
||||
|
||||
local function _read_multibulk(self, response)
|
||||
local str = response:sub(2)
|
||||
function response.multibulk(client, data)
|
||||
local str = data:sub(2)
|
||||
|
||||
-- TODO: add a check if the returned value is indeed a number
|
||||
local list_count = tonumber(str)
|
||||
@ -134,7 +89,7 @@ local function _read_multibulk(self, response)
|
||||
|
||||
if list_count > 0 then
|
||||
for i = 1, list_count do
|
||||
table.insert(list, i, _read_bulk(self, _read(self)))
|
||||
table.insert(list, i, response.bulk(client, network.read(client)))
|
||||
end
|
||||
end
|
||||
|
||||
@ -142,8 +97,8 @@ local function _read_multibulk(self, response)
|
||||
end
|
||||
end
|
||||
|
||||
local function _read_integer(self, response)
|
||||
local res = response:sub(2)
|
||||
function response.integer(client, data)
|
||||
local res = data:sub(2)
|
||||
local number = tonumber(res)
|
||||
|
||||
if not number then
|
||||
@ -157,100 +112,183 @@ local function _read_integer(self, response)
|
||||
return number
|
||||
end
|
||||
|
||||
-- ############################################################################
|
||||
|
||||
protocol.prefixes = {
|
||||
['+'] = _read_line,
|
||||
['-'] = _read_error,
|
||||
['$'] = _read_bulk,
|
||||
['*'] = _read_multibulk,
|
||||
[':'] = _read_integer,
|
||||
['+'] = response.status,
|
||||
['-'] = response.error,
|
||||
['$'] = response.bulk,
|
||||
['*'] = response.multibulk,
|
||||
[':'] = response.integer,
|
||||
}
|
||||
|
||||
-- ############################################################################
|
||||
|
||||
local methods = {
|
||||
function request.raw(client, buffer)
|
||||
-- TODO: optimize
|
||||
local bufferType = type(buffer)
|
||||
|
||||
if bufferType == 'string' then
|
||||
network.write(client, buffer)
|
||||
elseif bufferType == 'table' then
|
||||
network.write(client, table.concat(buffer))
|
||||
else
|
||||
error('Argument error: ' .. bufferType)
|
||||
end
|
||||
|
||||
return response.read(client)
|
||||
end
|
||||
|
||||
function request.inline(client, command, ...)
|
||||
if arg.n == 0 then
|
||||
network.write(client, command .. protocol.newline)
|
||||
else
|
||||
local arguments = arg
|
||||
arguments.n = nil
|
||||
|
||||
if #arguments > 0 then
|
||||
arguments = table.concat(arguments, ' ')
|
||||
else
|
||||
arguments = ''
|
||||
end
|
||||
|
||||
network.write(client, command .. ' ' .. arguments .. protocol.newline)
|
||||
end
|
||||
|
||||
return response.read(client)
|
||||
end
|
||||
|
||||
function request.bulk(client, command, ...)
|
||||
local arguments = arg
|
||||
local data = tostring(table.remove(arguments))
|
||||
arguments.n = nil
|
||||
|
||||
-- TODO: optimize
|
||||
if #arguments > 0 then
|
||||
arguments = table.concat(arguments, ' ')
|
||||
else
|
||||
arguments = ''
|
||||
end
|
||||
|
||||
return request.raw(client, {
|
||||
command, ' ', arguments, ' ', #data, protocol.newline, data, protocol.newline
|
||||
})
|
||||
end
|
||||
|
||||
-- ############################################################################
|
||||
|
||||
local function custom(command, send, parse)
|
||||
return function(self, ...)
|
||||
local reply = send(self, command, ...)
|
||||
if parse then
|
||||
return parse(reply, command, ...)
|
||||
else
|
||||
return reply
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function bulk(command, reader)
|
||||
return custom(command, request.bulk, reader)
|
||||
end
|
||||
|
||||
local function inline(command, reader)
|
||||
return custom(command, request.inline, reader)
|
||||
end
|
||||
|
||||
-- ############################################################################
|
||||
|
||||
function connect(host, port)
|
||||
local client_socket = socket.connect(host, port)
|
||||
if not client_socket then
|
||||
error('Could not connect to ' .. host .. ':' .. port)
|
||||
end
|
||||
|
||||
local redis_client = {
|
||||
socket = client_socket,
|
||||
raw_cmd = function(self, buffer)
|
||||
return request.raw(self, buffer .. protocol.newline)
|
||||
end,
|
||||
}
|
||||
|
||||
return load_methods(redis_client, redis_commands)
|
||||
end
|
||||
|
||||
-- ############################################################################
|
||||
|
||||
redis_commands = {
|
||||
-- miscellaneous commands
|
||||
ping = {
|
||||
'PING', _send_inline, function(response)
|
||||
ping = inline('PING',
|
||||
function(response)
|
||||
if response == 'PONG' then return true else return false end
|
||||
end
|
||||
},
|
||||
echo = { 'ECHO', _send_bulk },
|
||||
),
|
||||
echo = bulk('ECHO'),
|
||||
-- TODO: the server returns an empty -ERR on authentication failure
|
||||
auth = { 'AUTH' },
|
||||
auth = inline('AUTH'),
|
||||
|
||||
-- connection handling
|
||||
quit = { 'QUIT', function(self, command)
|
||||
_write(self, command .. protocol.newline)
|
||||
quit = custom('QUIT',
|
||||
function(client, command)
|
||||
-- let's fire and forget! the connection is closed as soon
|
||||
-- as the QUIT command is received by the server.
|
||||
network.write(client, command .. protocol.newline)
|
||||
end
|
||||
},
|
||||
),
|
||||
|
||||
-- commands operating on string values
|
||||
set = { 'SET', _send_bulk },
|
||||
set_preserve = { 'SETNX', _send_bulk, toboolean },
|
||||
get = { 'GET' },
|
||||
get_multiple = { 'MGET' },
|
||||
increment = { 'INCR' },
|
||||
increment_by = { 'INCRBY' },
|
||||
decrement = { 'DECR' },
|
||||
decrement_by = { 'DECRBY' },
|
||||
exists = { 'EXISTS', _send_inline, toboolean },
|
||||
delete = { 'DEL', _send_inline, toboolean },
|
||||
type = { 'TYPE' },
|
||||
set = bulk('SET'),
|
||||
set_preserve = bulk('SETNX', toboolean),
|
||||
get = inline('GET'),
|
||||
get_multiple = inline('MGET'),
|
||||
increment = inline('INCR'),
|
||||
increment_by = inline('INCRBY'),
|
||||
decrement = inline('DECR'),
|
||||
decrement_by = inline('DECRBY'),
|
||||
exists = inline('EXISTS', toboolean),
|
||||
delete = inline('DEL', toboolean),
|
||||
type = inline('TYPE'),
|
||||
|
||||
-- commands operating on the key space
|
||||
keys = {
|
||||
'KEYS', _send_inline, function(response)
|
||||
keys = inline('KEYS',
|
||||
function(response)
|
||||
local keys = {}
|
||||
response:gsub('%w+', function(key)
|
||||
table.insert(keys, key)
|
||||
end)
|
||||
return keys
|
||||
end
|
||||
},
|
||||
random_key = { 'RANDOMKEY' },
|
||||
rename = { 'RENAME' },
|
||||
rename_preserve = { 'RENAMENX' },
|
||||
database_size = { 'DBSIZE' },
|
||||
),
|
||||
random_key = inline('RANDOMKEY'),
|
||||
rename = inline('RENAME'),
|
||||
rename_preserve = inline('RENAMENX'),
|
||||
expire = inline('EXPIRE', toboolean),
|
||||
database_size = inline('DBSIZE'),
|
||||
|
||||
-- commands operating on lists
|
||||
push_tail = { 'RPUSH', _send_bulk },
|
||||
push_head = { 'LPUSH', _send_bulk },
|
||||
list_length = { 'LLEN', _send_inline, function(response, key)
|
||||
--[[ TODO: redis seems to return a -ERR when the specified key does
|
||||
not hold a list value, but this behaviour is not
|
||||
consistent with the specs docs. This might be due to the
|
||||
-ERR response paradigm being new, which supersedes the
|
||||
check for negative numbers to identify errors. ]]
|
||||
if response == -2 then
|
||||
error('Key ' .. key .. ' does not hold a list value')
|
||||
end
|
||||
return response
|
||||
end
|
||||
},
|
||||
list_range = { 'LRANGE' },
|
||||
list_trim = { 'LTRIM' },
|
||||
list_index = { 'LINDEX' },
|
||||
list_set = { 'LSET', _send_bulk },
|
||||
list_remove = { 'LREM', _send_bulk },
|
||||
pop_first = { 'LPOP' },
|
||||
pop_last = { 'RPOP' },
|
||||
push_tail = bulk('RPUSH'),
|
||||
push_head = bulk('LPUSH'),
|
||||
list_length = inline('LLEN'),
|
||||
list_range = inline('LRANGE'),
|
||||
list_trim = inline('LTRIM'),
|
||||
list_index = inline('LINDEX'),
|
||||
list_set = bulk('LSET'),
|
||||
list_remove = bulk('LREM'),
|
||||
pop_first = inline('LPOP'),
|
||||
pop_last = inline('RPOP'),
|
||||
|
||||
-- commands operating on sets
|
||||
set_add = { 'SADD' },
|
||||
set_remove = { 'SREM' },
|
||||
set_cardinality = { 'SCARD' },
|
||||
set_is_member = { 'SISMEMBER' },
|
||||
set_intersection = { 'SINTER' },
|
||||
set_intersection_store = { 'SINTERSTORE' },
|
||||
set_members = { 'SMEMBERS' },
|
||||
set_add = inline('SADD'),
|
||||
set_remove = inline('SREM'),
|
||||
set_cardinality = inline('SCARD'),
|
||||
set_is_member = inline('SISMEMBER'),
|
||||
set_intersection = inline('SINTER'),
|
||||
set_intersection_store = inline('SINTERSTORE'),
|
||||
set_members = inline('SMEMBERS'),
|
||||
|
||||
-- multiple databases handling commands
|
||||
select_database = { 'SELECT' },
|
||||
move_key = { 'MOVE' },
|
||||
flush_database = { 'FLUSHDB' },
|
||||
flush_databases = { 'FLUSHALL' },
|
||||
select_database = inline('SELECT'),
|
||||
move_key = inline('MOVE'),
|
||||
flush_database = inline('FLUSHDB'),
|
||||
flush_databases = inline('FLUSHALL'),
|
||||
|
||||
-- sorting
|
||||
--[[
|
||||
@ -262,20 +300,29 @@ local methods = {
|
||||
sort = { 'desc', 'alpha' }
|
||||
}
|
||||
--]]
|
||||
sort = { 'SORT' },
|
||||
sort = custom('SORT',
|
||||
function(client, command, params)
|
||||
-- TODO: here we will put the logic needed to serialize the params
|
||||
-- table to be sent as the argument of the SORT command.
|
||||
return request.inline(client, command, params)
|
||||
end
|
||||
),
|
||||
|
||||
-- persistence control commands
|
||||
save = { 'SAVE' },
|
||||
background_save = { 'BGSAVE' },
|
||||
last_save = { 'LASTSAVE' },
|
||||
shutdown = { 'SHUTDOWN', function(self, command)
|
||||
_write(self, command .. protocol.newline)
|
||||
save = inline('SAVE'),
|
||||
background_save = inline('BGSAVE'),
|
||||
last_save = inline('LASTSAVE'),
|
||||
shutdown = custom('SHUTDOWN',
|
||||
function(client, command)
|
||||
-- let's fire and forget! the connection is closed as soon
|
||||
-- as the SHUTDOWN command is received by the server.
|
||||
network.write(command .. protocol.newline)
|
||||
end
|
||||
},
|
||||
),
|
||||
|
||||
-- remote server control commands
|
||||
info = {
|
||||
'INFO', _send_inline, function(response)
|
||||
info = inline('INFO',
|
||||
function(response)
|
||||
local info = {}
|
||||
response:gsub('([^\r\n]*)\r\n', function(kv)
|
||||
local k,v = kv:match(('([^:]*):([^:]*)'):rep(1))
|
||||
@ -283,40 +330,5 @@ local methods = {
|
||||
end)
|
||||
return info
|
||||
end
|
||||
},
|
||||
),
|
||||
}
|
||||
|
||||
function connect(host, port)
|
||||
local client_socket = socket.connect(host, port)
|
||||
|
||||
if not client_socket then
|
||||
error('Could not connect to ' .. host .. ':' .. port)
|
||||
end
|
||||
|
||||
local redis_client = {
|
||||
socket = client_socket,
|
||||
raw_cmd = function(self, buffer)
|
||||
return _send_raw(self, buffer .. protocol.newline)
|
||||
end,
|
||||
}
|
||||
|
||||
return setmetatable(redis_client, {
|
||||
__index = function(self, method)
|
||||
local redis_meth = methods[method]
|
||||
if redis_meth then
|
||||
return function(self, ...)
|
||||
if not redis_meth[2] then
|
||||
table.insert(redis_meth, 2, _send_inline)
|
||||
end
|
||||
|
||||
local response = redis_meth[2](self, redis_meth[1], ...)
|
||||
if redis_meth[3] then
|
||||
return redis_meth[3](response, ...)
|
||||
else
|
||||
return response
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
})
|
||||
end
|
||||
|
Loading…
x
Reference in New Issue
Block a user