Lua client added thanks to Daniele Alessandri
This commit is contained in:
parent
e63943a450
commit
f2aa84bd63
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,3 +6,4 @@ redis-benchmark
|
|||||||
doc-tools
|
doc-tools
|
||||||
mkrelease.sh
|
mkrelease.sh
|
||||||
release
|
release
|
||||||
|
myredis.conf
|
||||||
|
@ -26,4 +26,7 @@ Perl lib source code:
|
|||||||
Redis-php PHP C module:
|
Redis-php PHP C module:
|
||||||
http://code.google.com/p/phpredis/
|
http://code.google.com/p/phpredis/
|
||||||
|
|
||||||
|
Lua lib source code:
|
||||||
|
http://github.com/nrk/redis-lua/tree/master
|
||||||
|
|
||||||
For all the rest check the Redis tarball or Git repository.
|
For all the rest check the Redis tarball or Git repository.
|
||||||
|
BIN
client-libraries/lua/.LICENSE.swp
Normal file
BIN
client-libraries/lua/.LICENSE.swp
Normal file
Binary file not shown.
22
client-libraries/lua/LICENSE
Normal file
22
client-libraries/lua/LICENSE
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
Copyright (c) 2009
|
||||||
|
Daniele Alessandri
|
||||||
|
http://www.clorophilla.net/
|
||||||
|
|
||||||
|
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.
|
4
client-libraries/lua/README
Normal file
4
client-libraries/lua/README
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
redis-lua
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
A Lua client library for the redis key value storage system.
|
322
client-libraries/lua/redis.lua
Normal file
322
client-libraries/lua/redis.lua
Normal file
@ -0,0 +1,322 @@
|
|||||||
|
module('Redis', package.seeall)
|
||||||
|
|
||||||
|
require('socket') -- requires LuaSocket as a dependency
|
||||||
|
|
||||||
|
-- ############################################################################
|
||||||
|
|
||||||
|
local protocol = {
|
||||||
|
newline = '\r\n', ok = 'OK', err = 'ERR', null = 'nil',
|
||||||
|
}
|
||||||
|
|
||||||
|
-- ############################################################################
|
||||||
|
|
||||||
|
local function toboolean(value)
|
||||||
|
return value == 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local function _write(self, buffer)
|
||||||
|
local _, err = self.socket:send(buffer)
|
||||||
|
if err then error(err) end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function _read(self, len)
|
||||||
|
if len == nil then len = '*l' end
|
||||||
|
local line, err = self.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)
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
|
||||||
|
if err_line:sub(1, 3) == protocol.err then
|
||||||
|
error("Redis error: " .. err_line:sub(5))
|
||||||
|
else
|
||||||
|
error("Redis error: " .. err_line)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function _read_bulk(self, response)
|
||||||
|
local str = response: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);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function _read_multibulk(self, response)
|
||||||
|
local str = response:sub(2)
|
||||||
|
|
||||||
|
-- TODO: add a check if the returned value is indeed a number
|
||||||
|
local list_count = tonumber(str)
|
||||||
|
|
||||||
|
if list_count == -1 then
|
||||||
|
return nil
|
||||||
|
else
|
||||||
|
local list = {}
|
||||||
|
|
||||||
|
if list_count > 0 then
|
||||||
|
for i = 1, list_count do
|
||||||
|
table.insert(list, i, _read_bulk(self, _read(self)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return list
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function _read_integer(self, response)
|
||||||
|
local res = response:sub(2)
|
||||||
|
local number = tonumber(res)
|
||||||
|
|
||||||
|
if not number then
|
||||||
|
if res == protocol.null then
|
||||||
|
return nil
|
||||||
|
else
|
||||||
|
error('Cannot parse ' .. res .. ' as numeric response.')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return number
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ############################################################################
|
||||||
|
|
||||||
|
protocol.prefixes = {
|
||||||
|
['+'] = _read_line,
|
||||||
|
['-'] = _read_error,
|
||||||
|
['$'] = _read_bulk,
|
||||||
|
['*'] = _read_multibulk,
|
||||||
|
[':'] = _read_integer,
|
||||||
|
}
|
||||||
|
|
||||||
|
-- ############################################################################
|
||||||
|
|
||||||
|
local methods = {
|
||||||
|
-- miscellaneous commands
|
||||||
|
ping = {
|
||||||
|
'PING', _send_inline, function(response)
|
||||||
|
if response == 'PONG' then return true else return false end
|
||||||
|
end
|
||||||
|
},
|
||||||
|
echo = { 'ECHO', _send_bulk },
|
||||||
|
-- TODO: the server returns an empty -ERR on authentication failure
|
||||||
|
auth = { 'AUTH' },
|
||||||
|
|
||||||
|
-- connection handling
|
||||||
|
quit = { 'QUIT', function(self, command)
|
||||||
|
_write(self, 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' },
|
||||||
|
|
||||||
|
-- commands operating on the key space
|
||||||
|
keys = {
|
||||||
|
'KEYS', _send_inline, 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' },
|
||||||
|
|
||||||
|
-- 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' },
|
||||||
|
|
||||||
|
-- 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' },
|
||||||
|
|
||||||
|
-- multiple databases handling commands
|
||||||
|
select_database = { 'SELECT' },
|
||||||
|
move_key = { 'MOVE' },
|
||||||
|
flush_database = { 'FLUSHDB' },
|
||||||
|
flush_databases = { 'FLUSHALL' },
|
||||||
|
|
||||||
|
-- sorting
|
||||||
|
--[[
|
||||||
|
TODO: should we pass sort parameters as a table? e.g:
|
||||||
|
params = {
|
||||||
|
by = 'weight_*',
|
||||||
|
get = 'object_*',
|
||||||
|
limit = { 0, 10 },
|
||||||
|
sort = { 'desc', 'alpha' }
|
||||||
|
}
|
||||||
|
--]]
|
||||||
|
sort = { 'SORT' },
|
||||||
|
|
||||||
|
-- persistence control commands
|
||||||
|
save = { 'SAVE' },
|
||||||
|
background_save = { 'BGSAVE' },
|
||||||
|
last_save = { 'LASTSAVE' },
|
||||||
|
shutdown = { 'SHUTDOWN', function(self, command)
|
||||||
|
_write(self, command .. protocol.newline)
|
||||||
|
end
|
||||||
|
},
|
||||||
|
|
||||||
|
-- remote server control commands
|
||||||
|
info = {
|
||||||
|
'INFO', _send_inline, function(response)
|
||||||
|
local info = {}
|
||||||
|
response:gsub('([^\r\n]*)\r\n', function(kv)
|
||||||
|
local k,v = kv:match(('([^:]*):([^:]*)'):rep(1))
|
||||||
|
info[k] = v
|
||||||
|
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
|
20
redis.conf
20
redis.conf
@ -62,6 +62,17 @@ databases 16
|
|||||||
|
|
||||||
# slaveof <masterip> <masterport>
|
# slaveof <masterip> <masterport>
|
||||||
|
|
||||||
|
################################## SECURITY ###################################
|
||||||
|
|
||||||
|
# Require clients to issue AUTH <PASSWORD> before processing any other
|
||||||
|
# commands. This might be useful in environments in which you do not trust
|
||||||
|
# others with access to the host running redis-server.
|
||||||
|
#
|
||||||
|
# This should stay commented out for backward compatibility and because most
|
||||||
|
# people do not need auth (e.g. they run their own servers).
|
||||||
|
|
||||||
|
#requirepass foobared
|
||||||
|
|
||||||
############################### ADVANCED CONFIG ###############################
|
############################### ADVANCED CONFIG ###############################
|
||||||
|
|
||||||
# Glue small output buffers together in order to send small replies in a
|
# Glue small output buffers together in order to send small replies in a
|
||||||
@ -74,12 +85,3 @@ glueoutputbuf yes
|
|||||||
# pool so it uses more CPU and can be a bit slower. Usually it's a good
|
# pool so it uses more CPU and can be a bit slower. Usually it's a good
|
||||||
# idea.
|
# idea.
|
||||||
shareobjects no
|
shareobjects no
|
||||||
|
|
||||||
# Require clients to issue AUTH <PASSWORD> before processing any other
|
|
||||||
# commands. This might be useful in environments in which you do not trust
|
|
||||||
# others with access to the host running redis-server.
|
|
||||||
#
|
|
||||||
# This should stay commented out for backward compatibility and because most
|
|
||||||
# people do not need auth (e.g. they run their own servers).
|
|
||||||
|
|
||||||
#requirepass foobared
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user