PageRenderTime 50ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 1ms

/src/redis.lua

http://github.com/nrk/redis-lua
Lua | 1203 lines | 981 code | 166 blank | 56 comment | 173 complexity | 53c349e4457a3166b9927d1704669257 MD5 | raw file
  1. local redis = {
  2. _VERSION = 'redis-lua 2.0.5-dev',
  3. _DESCRIPTION = 'A Lua client library for the redis key value storage system.',
  4. _COPYRIGHT = 'Copyright (C) 2009-2012 Daniele Alessandri',
  5. }
  6. -- The following line is used for backwards compatibility in order to keep the `Redis`
  7. -- global module name. Using `Redis` is now deprecated so you should explicitly assign
  8. -- the module to a local variable when requiring it: `local redis = require('redis')`.
  9. Redis = redis
  10. local unpack = _G.unpack or table.unpack
  11. local network, request, response = {}, {}, {}
  12. local defaults = {
  13. host = '127.0.0.1',
  14. port = 6379,
  15. tcp_nodelay = true,
  16. path = nil,
  17. }
  18. local function merge_defaults(parameters)
  19. if parameters == nil then
  20. parameters = {}
  21. end
  22. for k, v in pairs(defaults) do
  23. if parameters[k] == nil then
  24. parameters[k] = defaults[k]
  25. end
  26. end
  27. return parameters
  28. end
  29. local function parse_boolean(v)
  30. if v == '1' or v == 'true' or v == 'TRUE' then
  31. return true
  32. elseif v == '0' or v == 'false' or v == 'FALSE' then
  33. return false
  34. else
  35. return nil
  36. end
  37. end
  38. local function toboolean(value) return value == 1 end
  39. local function sort_request(client, command, key, params)
  40. --[[ params = {
  41. by = 'weight_*',
  42. get = 'object_*',
  43. limit = { 0, 10 },
  44. sort = 'desc',
  45. alpha = true,
  46. } ]]
  47. local query = { key }
  48. if params then
  49. if params.by then
  50. table.insert(query, 'BY')
  51. table.insert(query, params.by)
  52. end
  53. if type(params.limit) == 'table' then
  54. -- TODO: check for lower and upper limits
  55. table.insert(query, 'LIMIT')
  56. table.insert(query, params.limit[1])
  57. table.insert(query, params.limit[2])
  58. end
  59. if params.get then
  60. if (type(params.get) == 'table') then
  61. for _, getarg in pairs(params.get) do
  62. table.insert(query, 'GET')
  63. table.insert(query, getarg)
  64. end
  65. else
  66. table.insert(query, 'GET')
  67. table.insert(query, params.get)
  68. end
  69. end
  70. if params.sort then
  71. table.insert(query, params.sort)
  72. end
  73. if params.alpha == true then
  74. table.insert(query, 'ALPHA')
  75. end
  76. if params.store then
  77. table.insert(query, 'STORE')
  78. table.insert(query, params.store)
  79. end
  80. end
  81. request.multibulk(client, command, query)
  82. end
  83. local function zset_range_request(client, command, ...)
  84. local args, opts = {...}, { }
  85. if #args >= 1 and type(args[#args]) == 'table' then
  86. local options = table.remove(args, #args)
  87. if options.withscores then
  88. table.insert(opts, 'WITHSCORES')
  89. end
  90. end
  91. for _, v in pairs(opts) do table.insert(args, v) end
  92. request.multibulk(client, command, args)
  93. end
  94. local function zset_range_byscore_request(client, command, ...)
  95. local args, opts = {...}, { }
  96. if #args >= 1 and type(args[#args]) == 'table' then
  97. local options = table.remove(args, #args)
  98. if options.limit then
  99. table.insert(opts, 'LIMIT')
  100. table.insert(opts, options.limit.offset or options.limit[1])
  101. table.insert(opts, options.limit.count or options.limit[2])
  102. end
  103. if options.withscores then
  104. table.insert(opts, 'WITHSCORES')
  105. end
  106. end
  107. for _, v in pairs(opts) do table.insert(args, v) end
  108. request.multibulk(client, command, args)
  109. end
  110. local function zset_range_reply(reply, command, ...)
  111. local args = {...}
  112. local opts = args[4]
  113. if opts and (opts.withscores or string.lower(tostring(opts)) == 'withscores') then
  114. local new_reply = { }
  115. for i = 1, #reply, 2 do
  116. table.insert(new_reply, { reply[i], reply[i + 1] })
  117. end
  118. return new_reply
  119. else
  120. return reply
  121. end
  122. end
  123. local function zset_store_request(client, command, ...)
  124. local args, opts = {...}, { }
  125. if #args >= 1 and type(args[#args]) == 'table' then
  126. local options = table.remove(args, #args)
  127. if options.weights and type(options.weights) == 'table' then
  128. table.insert(opts, 'WEIGHTS')
  129. for _, weight in ipairs(options.weights) do
  130. table.insert(opts, weight)
  131. end
  132. end
  133. if options.aggregate then
  134. table.insert(opts, 'AGGREGATE')
  135. table.insert(opts, options.aggregate)
  136. end
  137. end
  138. for _, v in pairs(opts) do table.insert(args, v) end
  139. request.multibulk(client, command, args)
  140. end
  141. local function mset_filter_args(client, command, ...)
  142. local args, arguments = {...}, {}
  143. if (#args == 1 and type(args[1]) == 'table') then
  144. for k,v in pairs(args[1]) do
  145. table.insert(arguments, k)
  146. table.insert(arguments, v)
  147. end
  148. else
  149. arguments = args
  150. end
  151. request.multibulk(client, command, arguments)
  152. end
  153. local function hash_multi_request_builder(builder_callback)
  154. return function(client, command, ...)
  155. local args, arguments = {...}, { }
  156. if #args == 2 then
  157. table.insert(arguments, args[1])
  158. for k, v in pairs(args[2]) do
  159. builder_callback(arguments, k, v)
  160. end
  161. else
  162. arguments = args
  163. end
  164. request.multibulk(client, command, arguments)
  165. end
  166. end
  167. local function parse_info(response)
  168. local info = {}
  169. local current = info
  170. response:gsub('([^\r\n]*)\r\n', function(kv)
  171. if kv == '' then return end
  172. local section = kv:match('^# (%w+)$')
  173. if section then
  174. current = {}
  175. info[section:lower()] = current
  176. return
  177. end
  178. local k,v = kv:match(('([^:]*):([^:]*)'):rep(1))
  179. if k:match('db%d+') then
  180. current[k] = {}
  181. v:gsub(',', function(dbkv)
  182. local dbk,dbv = kv:match('([^:]*)=([^:]*)')
  183. current[k][dbk] = dbv
  184. end)
  185. else
  186. current[k] = v
  187. end
  188. end)
  189. return info
  190. end
  191. local function scan_request(client, command, ...)
  192. local args, req, params = {...}, { }, nil
  193. if command == 'SCAN' then
  194. table.insert(req, args[1])
  195. params = args[2]
  196. else
  197. table.insert(req, args[1])
  198. table.insert(req, args[2])
  199. params = args[3]
  200. end
  201. if params and params.match then
  202. table.insert(req, 'MATCH')
  203. table.insert(req, params.match)
  204. end
  205. if params and params.count then
  206. table.insert(req, 'COUNT')
  207. table.insert(req, params.count)
  208. end
  209. request.multibulk(client, command, req)
  210. end
  211. local zscan_response = function(reply, command, ...)
  212. local original, new = reply[2], { }
  213. for i = 1, #original, 2 do
  214. table.insert(new, { original[i], tonumber(original[i + 1]) })
  215. end
  216. reply[2] = new
  217. return reply
  218. end
  219. local hscan_response = function(reply, command, ...)
  220. local original, new = reply[2], { }
  221. for i = 1, #original, 2 do
  222. new[original[i]] = original[i + 1]
  223. end
  224. reply[2] = new
  225. return reply
  226. end
  227. local function load_methods(proto, commands)
  228. local client = setmetatable ({}, getmetatable(proto))
  229. for cmd, fn in pairs(commands) do
  230. if type(fn) ~= 'function' then
  231. redis.error('invalid type for command ' .. cmd .. '(must be a function)')
  232. end
  233. client[cmd] = fn
  234. end
  235. for i, v in pairs(proto) do
  236. client[i] = v
  237. end
  238. return client
  239. end
  240. local function create_client(proto, client_socket, commands)
  241. local client = load_methods(proto, commands)
  242. client.error = redis.error
  243. client.network = {
  244. socket = client_socket,
  245. read = network.read,
  246. write = network.write,
  247. }
  248. client.requests = {
  249. multibulk = request.multibulk,
  250. }
  251. return client
  252. end
  253. -- ############################################################################
  254. function network.write(client, buffer)
  255. local _, err = client.network.socket:send(buffer)
  256. if err then client.error(err) end
  257. end
  258. function network.read(client, len)
  259. if len == nil then len = '*l' end
  260. local line, err = client.network.socket:receive(len)
  261. if not err then return line else client.error('connection error: ' .. err) end
  262. end
  263. -- ############################################################################
  264. function response.read(client)
  265. local payload = client.network.read(client)
  266. local prefix, data = payload:sub(1, -#payload), payload:sub(2)
  267. -- status reply
  268. if prefix == '+' then
  269. if data == 'OK' then
  270. return true
  271. elseif data == 'QUEUED' then
  272. return { queued = true }
  273. else
  274. return data
  275. end
  276. -- error reply
  277. elseif prefix == '-' then
  278. return client.error('redis error: ' .. data)
  279. -- integer reply
  280. elseif prefix == ':' then
  281. local number = tonumber(data)
  282. if not number then
  283. if data == 'nil' then
  284. return nil
  285. end
  286. client.error('cannot parse ' .. data .. ' as a numeric response.')
  287. end
  288. return number
  289. -- bulk reply
  290. elseif prefix == '$' then
  291. local length = tonumber(data)
  292. if not length then
  293. client.error('cannot parse ' .. length .. ' as data length')
  294. end
  295. if length == -1 then
  296. return nil
  297. end
  298. local nextchunk = client.network.read(client, length + 2)
  299. return nextchunk:sub(1, -3)
  300. -- multibulk reply
  301. elseif prefix == '*' then
  302. local count = tonumber(data)
  303. if count == -1 then
  304. return nil
  305. end
  306. local list = {}
  307. if count > 0 then
  308. local reader = response.read
  309. for i = 1, count do
  310. list[i] = reader(client)
  311. end
  312. end
  313. return list
  314. -- unknown type of reply
  315. else
  316. return client.error('unknown response prefix: ' .. prefix)
  317. end
  318. end
  319. -- ############################################################################
  320. function request.raw(client, buffer)
  321. local bufferType = type(buffer)
  322. if bufferType == 'table' then
  323. client.network.write(client, table.concat(buffer))
  324. elseif bufferType == 'string' then
  325. client.network.write(client, buffer)
  326. else
  327. client.error('argument error: ' .. bufferType)
  328. end
  329. end
  330. function request.multibulk(client, command, ...)
  331. local args = {...}
  332. local argsn = #args
  333. local buffer = { true, true }
  334. if argsn == 1 and type(args[1]) == 'table' then
  335. argsn, args = #args[1], args[1]
  336. end
  337. buffer[1] = '*' .. tostring(argsn + 1) .. "\r\n"
  338. buffer[2] = '$' .. #command .. "\r\n" .. command .. "\r\n"
  339. local table_insert = table.insert
  340. for i = 1, argsn do
  341. local s_argument = tostring(args[i] or '')
  342. table_insert(buffer, '$' .. #s_argument .. "\r\n" .. s_argument .. "\r\n")
  343. end
  344. client.network.write(client, table.concat(buffer))
  345. end
  346. -- ############################################################################
  347. local function custom(command, send, parse)
  348. command = string.upper(command)
  349. return function(client, ...)
  350. send(client, command, ...)
  351. local reply = response.read(client)
  352. if type(reply) == 'table' and reply.queued then
  353. reply.parser = parse
  354. return reply
  355. else
  356. if parse then
  357. return parse(reply, command, ...)
  358. end
  359. return reply
  360. end
  361. end
  362. end
  363. local function command(command, opts)
  364. if opts == nil or type(opts) == 'function' then
  365. return custom(command, request.multibulk, opts)
  366. else
  367. return custom(command, opts.request or request.multibulk, opts.response)
  368. end
  369. end
  370. local define_command_impl = function(target, name, opts)
  371. local opts = opts or {}
  372. target[string.lower(name)] = custom(
  373. opts.command or string.upper(name),
  374. opts.request or request.multibulk,
  375. opts.response or nil
  376. )
  377. end
  378. local undefine_command_impl = function(target, name)
  379. target[string.lower(name)] = nil
  380. end
  381. -- ############################################################################
  382. local client_prototype = {}
  383. client_prototype.raw_cmd = function(client, buffer)
  384. request.raw(client, buffer .. "\r\n")
  385. return response.read(client)
  386. end
  387. -- obsolete
  388. client_prototype.define_command = function(client, name, opts)
  389. define_command_impl(client, name, opts)
  390. end
  391. -- obsolete
  392. client_prototype.undefine_command = function(client, name)
  393. undefine_command_impl(client, name)
  394. end
  395. client_prototype.quit = function(client)
  396. request.multibulk(client, 'QUIT')
  397. client.network.socket:shutdown()
  398. return true
  399. end
  400. client_prototype.shutdown = function(client)
  401. request.multibulk(client, 'SHUTDOWN')
  402. client.network.socket:shutdown()
  403. end
  404. -- Command pipelining
  405. client_prototype.pipeline = function(client, block)
  406. local requests, replies, parsers = {}, {}, {}
  407. local table_insert = table.insert
  408. local socket_write, socket_read = client.network.write, client.network.read
  409. client.network.write = function(_, buffer)
  410. table_insert(requests, buffer)
  411. end
  412. -- TODO: this hack is necessary to temporarily reuse the current
  413. -- request -> response handling implementation of redis-lua
  414. -- without further changes in the code, but it will surely
  415. -- disappear when the new command-definition infrastructure
  416. -- will finally be in place.
  417. client.network.read = function() return '+QUEUED' end
  418. local pipeline = setmetatable({}, {
  419. __index = function(env, name)
  420. local cmd = client[name]
  421. if not cmd then
  422. client.error('unknown redis command: ' .. name, 2)
  423. end
  424. return function(self, ...)
  425. local reply = cmd(client, ...)
  426. table_insert(parsers, #requests, reply.parser)
  427. return reply
  428. end
  429. end
  430. })
  431. local success, retval = pcall(block, pipeline)
  432. client.network.write, client.network.read = socket_write, socket_read
  433. if not success then client.error(retval, 0) end
  434. client.network.write(client, table.concat(requests, ''))
  435. for i = 1, #requests do
  436. local reply, parser = response.read(client), parsers[i]
  437. if parser then
  438. reply = parser(reply)
  439. end
  440. table_insert(replies, i, reply)
  441. end
  442. return replies, #requests
  443. end
  444. -- Publish/Subscribe
  445. do
  446. local channels = function(channels)
  447. if type(channels) == 'string' then
  448. channels = { channels }
  449. end
  450. return channels
  451. end
  452. local subscribe = function(client, ...)
  453. request.multibulk(client, 'subscribe', ...)
  454. end
  455. local psubscribe = function(client, ...)
  456. request.multibulk(client, 'psubscribe', ...)
  457. end
  458. local unsubscribe = function(client, ...)
  459. request.multibulk(client, 'unsubscribe')
  460. end
  461. local punsubscribe = function(client, ...)
  462. request.multibulk(client, 'punsubscribe')
  463. end
  464. local consumer_loop = function(client)
  465. local aborting, subscriptions = false, 0
  466. local abort = function()
  467. if not aborting then
  468. unsubscribe(client)
  469. punsubscribe(client)
  470. aborting = true
  471. end
  472. end
  473. return coroutine.wrap(function()
  474. while true do
  475. local message
  476. local response = response.read(client)
  477. if response[1] == 'pmessage' then
  478. message = {
  479. kind = response[1],
  480. pattern = response[2],
  481. channel = response[3],
  482. payload = response[4],
  483. }
  484. else
  485. message = {
  486. kind = response[1],
  487. channel = response[2],
  488. payload = response[3],
  489. }
  490. end
  491. if string.match(message.kind, '^p?subscribe$') then
  492. subscriptions = subscriptions + 1
  493. end
  494. if string.match(message.kind, '^p?unsubscribe$') then
  495. subscriptions = subscriptions - 1
  496. end
  497. if aborting and subscriptions == 0 then
  498. break
  499. end
  500. coroutine.yield(message, abort)
  501. end
  502. end)
  503. end
  504. client_prototype.pubsub = function(client, subscriptions)
  505. if type(subscriptions) == 'table' then
  506. if subscriptions.subscribe then
  507. subscribe(client, channels(subscriptions.subscribe))
  508. end
  509. if subscriptions.psubscribe then
  510. psubscribe(client, channels(subscriptions.psubscribe))
  511. end
  512. end
  513. return consumer_loop(client)
  514. end
  515. end
  516. -- Redis transactions (MULTI/EXEC)
  517. do
  518. local function identity(...) return ... end
  519. local emptytable = {}
  520. local function initialize_transaction(client, options, block, queued_parsers)
  521. local table_insert = table.insert
  522. local coro = coroutine.create(block)
  523. if options.watch then
  524. local watch_keys = {}
  525. for _, key in pairs(options.watch) do
  526. table_insert(watch_keys, key)
  527. end
  528. if #watch_keys > 0 then
  529. client:watch(unpack(watch_keys))
  530. end
  531. end
  532. local transaction_client = setmetatable({}, {__index=client})
  533. transaction_client.exec = function(...)
  534. client.error('cannot use EXEC inside a transaction block')
  535. end
  536. transaction_client.multi = function(...)
  537. coroutine.yield()
  538. end
  539. transaction_client.commands_queued = function()
  540. return #queued_parsers
  541. end
  542. assert(coroutine.resume(coro, transaction_client))
  543. transaction_client.multi = nil
  544. transaction_client.discard = function(...)
  545. local reply = client:discard()
  546. for i, v in pairs(queued_parsers) do
  547. queued_parsers[i]=nil
  548. end
  549. coro = initialize_transaction(client, options, block, queued_parsers)
  550. return reply
  551. end
  552. transaction_client.watch = function(...)
  553. client.error('WATCH inside MULTI is not allowed')
  554. end
  555. setmetatable(transaction_client, { __index = function(t, k)
  556. local cmd = client[k]
  557. if type(cmd) == "function" then
  558. local function queuey(self, ...)
  559. local reply = cmd(client, ...)
  560. assert((reply or emptytable).queued == true, 'a QUEUED reply was expected')
  561. table_insert(queued_parsers, reply.parser or identity)
  562. return reply
  563. end
  564. t[k]=queuey
  565. return queuey
  566. else
  567. return cmd
  568. end
  569. end
  570. })
  571. client:multi()
  572. return coro
  573. end
  574. local function transaction(client, options, coroutine_block, attempts)
  575. local queued_parsers, replies = {}, {}
  576. local retry = tonumber(attempts) or tonumber(options.retry) or 2
  577. local coro = initialize_transaction(client, options, coroutine_block, queued_parsers)
  578. local success, retval
  579. if coroutine.status(coro) == 'suspended' then
  580. success, retval = coroutine.resume(coro)
  581. else
  582. -- do not fail if the coroutine has not been resumed (missing t:multi() with CAS)
  583. success, retval = true, 'empty transaction'
  584. end
  585. if #queued_parsers == 0 or not success then
  586. client:discard()
  587. assert(success, retval)
  588. return replies, 0
  589. end
  590. local raw_replies = client:exec()
  591. if not raw_replies then
  592. if (retry or 0) <= 0 then
  593. client.error("MULTI/EXEC transaction aborted by the server")
  594. else
  595. --we're not quite done yet
  596. return transaction(client, options, coroutine_block, retry - 1)
  597. end
  598. end
  599. local table_insert = table.insert
  600. for i, parser in pairs(queued_parsers) do
  601. table_insert(replies, i, parser(raw_replies[i]))
  602. end
  603. return replies, #queued_parsers
  604. end
  605. client_prototype.transaction = function(client, arg1, arg2)
  606. local options, block
  607. if not arg2 then
  608. options, block = {}, arg1
  609. elseif arg1 then --and arg2, implicitly
  610. options, block = type(arg1)=="table" and arg1 or { arg1 }, arg2
  611. else
  612. client.error("Invalid parameters for redis transaction.")
  613. end
  614. if not options.watch then
  615. local watch_keys = { }
  616. for i, v in pairs(options) do
  617. if tonumber(i) then
  618. table.insert(watch_keys, v)
  619. options[i] = nil
  620. end
  621. end
  622. options.watch = watch_keys
  623. elseif not (type(options.watch) == 'table') then
  624. options.watch = { options.watch }
  625. end
  626. if not options.cas then
  627. local tx_block = block
  628. block = function(client, ...)
  629. client:multi()
  630. return tx_block(client, ...) --can't wrap this in pcall because we're in a coroutine.
  631. end
  632. end
  633. return transaction(client, options, block)
  634. end
  635. end
  636. -- MONITOR context
  637. do
  638. local monitor_loop = function(client)
  639. local monitoring = true
  640. -- Tricky since the payload format changed starting from Redis 2.6.
  641. local pattern = '^(%d+%.%d+)( ?.- ?) ?"(%a+)" ?(.-)$'
  642. local abort = function()
  643. monitoring = false
  644. end
  645. return coroutine.wrap(function()
  646. client:monitor()
  647. while monitoring do
  648. local message, matched
  649. local response = response.read(client)
  650. local ok = response:gsub(pattern, function(time, info, cmd, args)
  651. message = {
  652. timestamp = tonumber(time),
  653. client = info:match('%d+.%d+.%d+.%d+:%d+'),
  654. database = tonumber(info:match('%d+')) or 0,
  655. command = cmd,
  656. arguments = args:match('.+'),
  657. }
  658. matched = true
  659. end)
  660. if not matched then
  661. client.error('Unable to match MONITOR payload: '..response)
  662. end
  663. coroutine.yield(message, abort)
  664. end
  665. end)
  666. end
  667. client_prototype.monitor_messages = function(client)
  668. return monitor_loop(client)
  669. end
  670. end
  671. -- ############################################################################
  672. local function connect_tcp(socket, parameters)
  673. local host, port = parameters.host, tonumber(parameters.port)
  674. if parameters.timeout then
  675. socket:settimeout(parameters.timeout, 't')
  676. end
  677. local ok, err = socket:connect(host, port)
  678. if not ok then
  679. redis.error('could not connect to '..host..':'..port..' ['..err..']')
  680. end
  681. socket:setoption('tcp-nodelay', parameters.tcp_nodelay)
  682. return socket
  683. end
  684. local function connect_unix(socket, parameters)
  685. local ok, err = socket:connect(parameters.path)
  686. if not ok then
  687. redis.error('could not connect to '..parameters.path..' ['..err..']')
  688. end
  689. return socket
  690. end
  691. local function create_connection(parameters)
  692. if parameters.socket then
  693. return parameters.socket
  694. end
  695. local perform_connection, socket
  696. if parameters.scheme == 'unix' then
  697. perform_connection, socket = connect_unix, require('socket.unix')
  698. assert(socket, 'your build of LuaSocket does not support UNIX domain sockets')
  699. else
  700. if parameters.scheme then
  701. local scheme = parameters.scheme
  702. assert(scheme == 'redis' or scheme == 'tcp', 'invalid scheme: '..scheme)
  703. end
  704. perform_connection, socket = connect_tcp, require('socket').tcp
  705. end
  706. return perform_connection(socket(), parameters)
  707. end
  708. -- ############################################################################
  709. function redis.error(message, level)
  710. error(message, (level or 1) + 1)
  711. end
  712. function redis.connect(...)
  713. local args, parameters = {...}, nil
  714. if #args == 1 then
  715. if type(args[1]) == 'table' then
  716. parameters = args[1]
  717. else
  718. local uri = require('socket.url')
  719. parameters = uri.parse(select(1, ...))
  720. if parameters.scheme then
  721. if parameters.query then
  722. for k, v in parameters.query:gmatch('([-_%w]+)=([-_%w]+)') do
  723. if k == 'tcp_nodelay' or k == 'tcp-nodelay' then
  724. parameters.tcp_nodelay = parse_boolean(v)
  725. elseif k == 'timeout' then
  726. parameters.timeout = tonumber(v)
  727. end
  728. end
  729. end
  730. else
  731. parameters.host = parameters.path
  732. end
  733. end
  734. elseif #args > 1 then
  735. local host, port, timeout = unpack(args)
  736. parameters = { host = host, port = port, timeout = tonumber(timeout) }
  737. end
  738. local commands = redis.commands or {}
  739. if type(commands) ~= 'table' then
  740. redis.error('invalid type for the commands table')
  741. end
  742. local socket = create_connection(merge_defaults(parameters))
  743. local client = create_client(client_prototype, socket, commands)
  744. return client
  745. end
  746. function redis.command(cmd, opts)
  747. return command(cmd, opts)
  748. end
  749. -- obsolete
  750. function redis.define_command(name, opts)
  751. define_command_impl(redis.commands, name, opts)
  752. end
  753. -- obsolete
  754. function redis.undefine_command(name)
  755. undefine_command_impl(redis.commands, name)
  756. end
  757. -- ############################################################################
  758. -- Commands defined in this table do not take the precedence over
  759. -- methods defined in the client prototype table.
  760. redis.commands = {
  761. -- commands operating on the key space
  762. exists = command('EXISTS', {
  763. response = toboolean
  764. }),
  765. del = command('DEL'),
  766. type = command('TYPE'),
  767. rename = command('RENAME'),
  768. renamenx = command('RENAMENX', {
  769. response = toboolean
  770. }),
  771. expire = command('EXPIRE', {
  772. response = toboolean
  773. }),
  774. pexpire = command('PEXPIRE', { -- >= 2.6
  775. response = toboolean
  776. }),
  777. expireat = command('EXPIREAT', {
  778. response = toboolean
  779. }),
  780. pexpireat = command('PEXPIREAT', { -- >= 2.6
  781. response = toboolean
  782. }),
  783. ttl = command('TTL'),
  784. pttl = command('PTTL'), -- >= 2.6
  785. move = command('MOVE', {
  786. response = toboolean
  787. }),
  788. dbsize = command('DBSIZE'),
  789. persist = command('PERSIST', { -- >= 2.2
  790. response = toboolean
  791. }),
  792. keys = command('KEYS', {
  793. response = function(response)
  794. if type(response) == 'string' then
  795. -- backwards compatibility path for Redis < 2.0
  796. local keys = {}
  797. response:gsub('[^%s]+', function(key)
  798. table.insert(keys, key)
  799. end)
  800. response = keys
  801. end
  802. return response
  803. end
  804. }),
  805. randomkey = command('RANDOMKEY'),
  806. sort = command('SORT', {
  807. request = sort_request,
  808. }),
  809. scan = command('SCAN', { -- >= 2.8
  810. request = scan_request,
  811. }),
  812. -- commands operating on string values
  813. set = command('SET'),
  814. setnx = command('SETNX', {
  815. response = toboolean
  816. }),
  817. setex = command('SETEX'), -- >= 2.0
  818. psetex = command('PSETEX'), -- >= 2.6
  819. mset = command('MSET', {
  820. request = mset_filter_args
  821. }),
  822. msetnx = command('MSETNX', {
  823. request = mset_filter_args,
  824. response = toboolean
  825. }),
  826. get = command('GET'),
  827. mget = command('MGET'),
  828. getset = command('GETSET'),
  829. incr = command('INCR'),
  830. incrby = command('INCRBY'),
  831. incrbyfloat = command('INCRBYFLOAT', { -- >= 2.6
  832. response = function(reply, command, ...)
  833. return tonumber(reply)
  834. end,
  835. }),
  836. decr = command('DECR'),
  837. decrby = command('DECRBY'),
  838. append = command('APPEND'), -- >= 2.0
  839. substr = command('SUBSTR'), -- >= 2.0
  840. strlen = command('STRLEN'), -- >= 2.2
  841. setrange = command('SETRANGE'), -- >= 2.2
  842. getrange = command('GETRANGE'), -- >= 2.2
  843. setbit = command('SETBIT'), -- >= 2.2
  844. getbit = command('GETBIT'), -- >= 2.2
  845. bitop = command('BITOP'), -- >= 2.6
  846. bitcount = command('BITCOUNT'), -- >= 2.6
  847. -- commands operating on lists
  848. rpush = command('RPUSH'),
  849. lpush = command('LPUSH'),
  850. llen = command('LLEN'),
  851. lrange = command('LRANGE'),
  852. ltrim = command('LTRIM'),
  853. lindex = command('LINDEX'),
  854. lset = command('LSET'),
  855. lrem = command('LREM'),
  856. lpop = command('LPOP'),
  857. rpop = command('RPOP'),
  858. rpoplpush = command('RPOPLPUSH'),
  859. blpop = command('BLPOP'), -- >= 2.0
  860. brpop = command('BRPOP'), -- >= 2.0
  861. rpushx = command('RPUSHX'), -- >= 2.2
  862. lpushx = command('LPUSHX'), -- >= 2.2
  863. linsert = command('LINSERT'), -- >= 2.2
  864. brpoplpush = command('BRPOPLPUSH'), -- >= 2.2
  865. -- commands operating on sets
  866. sadd = command('SADD'),
  867. srem = command('SREM'),
  868. spop = command('SPOP'),
  869. smove = command('SMOVE', {
  870. response = toboolean
  871. }),
  872. scard = command('SCARD'),
  873. sismember = command('SISMEMBER', {
  874. response = toboolean
  875. }),
  876. sinter = command('SINTER'),
  877. sinterstore = command('SINTERSTORE'),
  878. sunion = command('SUNION'),
  879. sunionstore = command('SUNIONSTORE'),
  880. sdiff = command('SDIFF'),
  881. sdiffstore = command('SDIFFSTORE'),
  882. smembers = command('SMEMBERS'),
  883. srandmember = command('SRANDMEMBER'),
  884. sscan = command('SSCAN', { -- >= 2.8
  885. request = scan_request,
  886. }),
  887. -- commands operating on sorted sets
  888. zadd = command('ZADD'),
  889. zincrby = command('ZINCRBY', {
  890. response = function(reply, command, ...)
  891. return tonumber(reply)
  892. end,
  893. }),
  894. zrem = command('ZREM'),
  895. zrange = command('ZRANGE', {
  896. request = zset_range_request,
  897. response = zset_range_reply,
  898. }),
  899. zrevrange = command('ZREVRANGE', {
  900. request = zset_range_request,
  901. response = zset_range_reply,
  902. }),
  903. zrangebyscore = command('ZRANGEBYSCORE', {
  904. request = zset_range_byscore_request,
  905. response = zset_range_reply,
  906. }),
  907. zrevrangebyscore = command('ZREVRANGEBYSCORE', { -- >= 2.2
  908. request = zset_range_byscore_request,
  909. response = zset_range_reply,
  910. }),
  911. zunionstore = command('ZUNIONSTORE', { -- >= 2.0
  912. request = zset_store_request
  913. }),
  914. zinterstore = command('ZINTERSTORE', { -- >= 2.0
  915. request = zset_store_request
  916. }),
  917. zcount = command('ZCOUNT'),
  918. zcard = command('ZCARD'),
  919. zscore = command('ZSCORE'),
  920. zremrangebyscore = command('ZREMRANGEBYSCORE'),
  921. zrank = command('ZRANK'), -- >= 2.0
  922. zrevrank = command('ZREVRANK'), -- >= 2.0
  923. zremrangebyrank = command('ZREMRANGEBYRANK'), -- >= 2.0
  924. zscan = command('ZSCAN', { -- >= 2.8
  925. request = scan_request,
  926. response = zscan_response,
  927. }),
  928. -- commands operating on hashes
  929. hset = command('HSET', { -- >= 2.0
  930. response = toboolean
  931. }),
  932. hsetnx = command('HSETNX', { -- >= 2.0
  933. response = toboolean
  934. }),
  935. hmset = command('HMSET', { -- >= 2.0
  936. request = hash_multi_request_builder(function(args, k, v)
  937. table.insert(args, k)
  938. table.insert(args, v)
  939. end),
  940. }),
  941. hincrby = command('HINCRBY'), -- >= 2.0
  942. hincrbyfloat = command('HINCRBYFLOAT', {-- >= 2.6
  943. response = function(reply, command, ...)
  944. return tonumber(reply)
  945. end,
  946. }),
  947. hget = command('HGET'), -- >= 2.0
  948. hmget = command('HMGET', { -- >= 2.0
  949. request = hash_multi_request_builder(function(args, k, v)
  950. table.insert(args, v)
  951. end),
  952. }),
  953. hdel = command('HDEL'), -- >= 2.0
  954. hexists = command('HEXISTS', { -- >= 2.0
  955. response = toboolean
  956. }),
  957. hlen = command('HLEN'), -- >= 2.0
  958. hkeys = command('HKEYS'), -- >= 2.0
  959. hvals = command('HVALS'), -- >= 2.0
  960. hgetall = command('HGETALL', { -- >= 2.0
  961. response = function(reply, command, ...)
  962. local new_reply = { }
  963. for i = 1, #reply, 2 do new_reply[reply[i]] = reply[i + 1] end
  964. return new_reply
  965. end
  966. }),
  967. hscan = command('HSCAN', { -- >= 2.8
  968. request = scan_request,
  969. response = hscan_response,
  970. }),
  971. -- connection related commands
  972. ping = command('PING', {
  973. response = function(response) return response == 'PONG' end
  974. }),
  975. echo = command('ECHO'),
  976. auth = command('AUTH'),
  977. select = command('SELECT'),
  978. -- transactions
  979. multi = command('MULTI'), -- >= 2.0
  980. exec = command('EXEC'), -- >= 2.0
  981. discard = command('DISCARD'), -- >= 2.0
  982. watch = command('WATCH'), -- >= 2.2
  983. unwatch = command('UNWATCH'), -- >= 2.2
  984. -- publish - subscribe
  985. subscribe = command('SUBSCRIBE'), -- >= 2.0
  986. unsubscribe = command('UNSUBSCRIBE'), -- >= 2.0
  987. psubscribe = command('PSUBSCRIBE'), -- >= 2.0
  988. punsubscribe = command('PUNSUBSCRIBE'), -- >= 2.0
  989. publish = command('PUBLISH'), -- >= 2.0
  990. -- redis scripting
  991. eval = command('EVAL'), -- >= 2.6
  992. evalsha = command('EVALSHA'), -- >= 2.6
  993. script = command('SCRIPT'), -- >= 2.6
  994. -- remote server control commands
  995. bgrewriteaof = command('BGREWRITEAOF'),
  996. config = command('CONFIG', { -- >= 2.0
  997. response = function(reply, command, ...)
  998. if (type(reply) == 'table') then
  999. local new_reply = { }
  1000. for i = 1, #reply, 2 do new_reply[reply[i]] = reply[i + 1] end
  1001. return new_reply
  1002. end
  1003. return reply
  1004. end
  1005. }),
  1006. client = command('CLIENT'), -- >= 2.4
  1007. slaveof = command('SLAVEOF'),
  1008. save = command('SAVE'),
  1009. bgsave = command('BGSAVE'),
  1010. lastsave = command('LASTSAVE'),
  1011. flushdb = command('FLUSHDB'),
  1012. flushall = command('FLUSHALL'),
  1013. monitor = command('MONITOR'),
  1014. time = command('TIME'), -- >= 2.6
  1015. slowlog = command('SLOWLOG', { -- >= 2.2.13
  1016. response = function(reply, command, ...)
  1017. if (type(reply) == 'table') then
  1018. local structured = { }
  1019. for index, entry in ipairs(reply) do
  1020. structured[index] = {
  1021. id = tonumber(entry[1]),
  1022. timestamp = tonumber(entry[2]),
  1023. duration = tonumber(entry[3]),
  1024. command = entry[4],
  1025. }
  1026. end
  1027. return structured
  1028. end
  1029. return reply
  1030. end
  1031. }),
  1032. info = command('INFO', {
  1033. response = parse_info,
  1034. }),
  1035. }
  1036. -- ############################################################################
  1037. return redis