Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
ruanhaishen
redis
Commits
928394cd
Commit
928394cd
authored
Apr 08, 2009
by
antirez
Browse files
Lua client updated
parent
8b382972
Changes
2
Hide whitespace changes
Inline
Side-by-side
client-libraries/README
View file @
928394cd
...
...
@@ -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.
client-libraries/lua/redis.lua
View file @
928394cd
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
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
local
function
toboolean
(
value
)
return
value
==
1
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
function
response
.
status
(
client
,
data
)
local
sub
=
data
:
sub
(
2
)
if
sub
==
protocol
.
ok
then
return
true
else
return
sub
end
end
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
))
else
error
(
"Redis error: "
..
err_line
)
end
end
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
next_chunk
=
network
.
read
(
client
,
len
+
2
)
return
next_chunk
:
sub
(
1
,
-
3
);
end
end
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
)
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
,
response
.
bulk
(
client
,
network
.
read
(
client
)))
end
end
return
list
end
end
function
response
.
integer
(
client
,
data
)
local
res
=
data
:
sub
(
2
)
local
number
=
tonumber
(
res
)
local
function
_send_raw
(
self
,
buffer
)
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
=
{
[
'+'
]
=
response
.
status
,
[
'-'
]
=
response
.
error
,
[
'$'
]
=
response
.
bulk
,
[
'*'
]
=
response
.
multibulk
,
[
':'
]
=
response
.
integer
,
}
-- ############################################################################
function
request
.
raw
(
client
,
buffer
)
-- TODO: optimize
local
bufferType
=
type
(
buffer
)
if
bufferType
==
'string'
then
_write
(
self
,
buffer
)
network
.
write
(
client
,
buffer
)
elseif
bufferType
==
'table'
then
_write
(
self
,
table.concat
(
buffer
))
network
.
write
(
client
,
table.concat
(
buffer
))
else
error
(
'Argument error: '
..
bufferType
)
end
return
_read_
response
(
self
)
return
response
.
read
(
client
)
end
local
function
_send_
inline
(
self
,
command
,
...
)
function
request
.
inline
(
client
,
command
,
...
)
if
arg
.
n
==
0
then
_write
(
self
,
command
..
protocol
.
newline
)
network
.
write
(
client
,
command
..
protocol
.
newline
)
else
local
arguments
=
arg
arguments
.
n
=
nil
...
...
@@ -70,13 +150,13 @@ local function _send_inline(self, command, ...)
arguments
=
''
end
_write
(
self
,
command
..
' '
..
arguments
..
protocol
.
newline
)
network
.
write
(
client
,
command
..
' '
..
arguments
..
protocol
.
newline
)
end
return
_read_
response
(
self
)
return
response
.
read
(
client
)
end
local
function
_send_bulk
(
self
,
command
,
...
)
function
request
.
bulk
(
client
,
command
,
...
)
local
arguments
=
arg
local
data
=
tostring
(
table.remove
(
arguments
))
arguments
.
n
=
nil
...
...
@@ -88,169 +168,127 @@ local function _send_bulk(self, command, ...)
arguments
=
''
end
return
_send_raw
(
self
,
{
return
request
.
raw
(
client
,
{
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
)
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
_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
local
function
bulk
(
command
,
reader
)
return
custom
(
command
,
request
.
bulk
,
reader
)
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
local
function
inline
(
command
,
reader
)
return
custom
(
command
,
request
.
inline
,
reader
)
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
function
connect
(
host
,
port
)
local
client_socket
=
socket
.
connect
(
host
,
port
)
if
not
client_socket
then
error
(
'Could not connect to '
..
host
..
':'
..
port
)
end
return
number
end
-- ############################################################################
local
redis_client
=
{
socket
=
client_socket
,
raw_cmd
=
function
(
self
,
buffer
)
return
request
.
raw
(
self
,
buffer
..
protocol
.
newline
)
end
,
}
protocol
.
prefixes
=
{
[
'+'
]
=
_read_line
,
[
'-'
]
=
_read_error
,
[
'$'
]
=
_read_bulk
,
[
'*'
]
=
_read_multibulk
,
[
':'
]
=
_read_integer
,
}
return
load_methods
(
redis_client
,
redis_commands
)
end
-- ############################################################################
local
metho
ds
=
{
redis_comman
ds
=
{
-- 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
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment