diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 3db8a8f..70e3a1d 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -46,7 +46,7 @@ jobs: if: runner.os != 'Windows' uses: shogo82148/actions-setup-redis@v1 with: - redis-version: "4.x" + redis-version: "6.x" - name: Start Redis (windows) if: runner.os == 'Windows' diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index 0a9a983..e94fa07 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -27,7 +27,7 @@ jobs: - name: Start Redis uses: shogo82148/actions-setup-redis@v1 with: - redis-version: "4.x" + redis-version: "6.x" - name: "[macOS] system dependencies" if: runner.os == 'macOS' diff --git a/.gitignore b/.gitignore index 03643eb..d40700f 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,8 @@ autobrew docs/ doc Meta +*.so +*.o +*.gcov +*.gcda +*.gcno diff --git a/DESCRIPTION b/DESCRIPTION index c012261..032d01e 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: redux Title: R Bindings to 'hiredis' -Version: 1.1.2 +Version: 1.2.0 Authors@R: c(person("Rich", "FitzJohn", role = c("aut", "cre"), email = "rich.fitzjohn@gmail.com")) Description: A 'hiredis' wrapper that includes support for diff --git a/Makefile b/Makefile index 55e4f03..4ae1c3d 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ PACKAGE := $(shell grep '^Package:' DESCRIPTION | sed -E 's/^Package:[[:space:]]+//') -RSCRIPT = Rscript --no-init-file +RSCRIPT = Rscript all: compile_dll diff --git a/NEWS.md b/NEWS.md index faece56..752c719 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,7 @@ +## redux 1.2.0 + +* Add support for most of the new features in redis 6.x + ## redux 1.1.1 * More relaxed, though technically incorrect, URL parsing that allows undercores in the host name - useful for docker containers (#20). diff --git a/R/redis_api_generated.R b/R/redis_api_generated.R index 2b9af72..5114c8b 100644 --- a/R/redis_api_generated.R +++ b/R/redis_api_generated.R @@ -1,19 +1,62 @@ redis_commands <- function(command) { list( + ACL_LOAD = function() { + command(list("ACL", "LOAD")) + }, + ACL_SAVE = function() { + command(list("ACL", "SAVE")) + }, + ACL_LIST = function() { + command(list("ACL", "LIST")) + }, + ACL_USERS = function() { + command(list("ACL", "USERS")) + }, + ACL_GETUSER = function(username) { + assert_scalar2(username) + command(list("ACL", "GETUSER", username)) + }, + ACL_SETUSER = function(username, rule = NULL) { + assert_scalar2(username) + command(list("ACL", "SETUSER", username, rule)) + }, + ACL_DELUSER = function(username) { + command(list("ACL", "DELUSER", username)) + }, + ACL_CAT = function(categoryname = NULL) { + assert_scalar_or_null2(categoryname) + command(list("ACL", "CAT", categoryname)) + }, + ACL_GENPASS = function(bits = NULL) { + assert_scalar_or_null2(bits) + command(list("ACL", "GENPASS", bits)) + }, + ACL_WHOAMI = function() { + command(list("ACL", "WHOAMI")) + }, + ACL_LOG = function(count_or_RESET = NULL) { + assert_scalar_or_null2(count_or_RESET) + command(list("ACL", "LOG", count_or_RESET)) + }, + ACL_HELP = function() { + command(list("ACL", "HELP")) + }, APPEND = function(key, value) { assert_scalar2(key) assert_scalar2(value) command(list("APPEND", key, value)) }, - AUTH = function(password) { + AUTH = function(password, username = NULL) { + assert_scalar_or_null2(username) assert_scalar2(password) - command(list("AUTH", password)) + command(list("AUTH", username, password)) }, BGREWRITEAOF = function() { command(list("BGREWRITEAOF")) }, - BGSAVE = function() { - command(list("BGSAVE")) + BGSAVE = function(schedule = NULL) { + assert_match_value_or_null(schedule, c("SCHEDULE")) + command(list("BGSAVE", schedule)) }, BITCOUNT = function(key, start = NULL, end = NULL) { assert_scalar2(key) @@ -55,34 +98,84 @@ redis_commands <- function(command) { assert_scalar2(timeout) command(list("BRPOPLPUSH", source, destination, timeout)) }, - CLIENT_KILL = function(ip_port = NULL, ID = NULL, TYPE = NULL, ADDR = NULL, SKIPME = NULL) { + BLMOVE = function(source, destination, wherefrom, whereto, timeout) { + assert_scalar2(source) + assert_scalar2(destination) + assert_match_value(wherefrom, c("LEFT", "RIGHT")) + assert_match_value(whereto, c("LEFT", "RIGHT")) + assert_scalar2(timeout) + command(list("BLMOVE", source, destination, wherefrom, whereto, timeout)) + }, + BZPOPMIN = function(key, timeout) { + assert_scalar2(timeout) + command(list("BZPOPMIN", key, timeout)) + }, + BZPOPMAX = function(key, timeout) { + assert_scalar2(timeout) + command(list("BZPOPMAX", key, timeout)) + }, + CLIENT_CACHING = function(mode) { + stop("Do not use CLIENT CACHING; not supported with this client") + }, + CLIENT_ID = function() { + command(list("CLIENT", "ID")) + }, + CLIENT_INFO = function() { + command(list("CLIENT", "INFO")) + }, + CLIENT_KILL = function(ip_port = NULL, ID = NULL, TYPE = NULL, USER = NULL, ADDR = NULL, LADDR = NULL, SKIPME = NULL) { assert_scalar_or_null2(ip_port) assert_scalar_or_null2(ID) assert_match_value_or_null(TYPE, c("normal", "master", "slave", "pubsub")) + assert_scalar_or_null2(USER) assert_scalar_or_null2(ADDR) + assert_scalar_or_null2(LADDR) assert_scalar_or_null2(SKIPME) - command(list("CLIENT", "KILL", ip_port, cmd_command("ID", ID, FALSE), cmd_command("TYPE", TYPE, FALSE), cmd_command("ADDR", ADDR, FALSE), cmd_command("SKIPME", SKIPME, FALSE))) + command(list("CLIENT", "KILL", ip_port, cmd_command("ID", ID, FALSE), cmd_command("TYPE", TYPE, FALSE), cmd_command("USER", USER, FALSE), cmd_command("ADDR", ADDR, FALSE), cmd_command("LADDR", LADDR, FALSE), cmd_command("SKIPME", SKIPME, FALSE))) }, - CLIENT_LIST = function() { - command(list("CLIENT", "LIST")) + CLIENT_LIST = function(TYPE = NULL, id = NULL) { + assert_match_value_or_null(TYPE, c("normal", "master", "replica", "pubsub")) + assert_scalar_or_null2(id) + command(list("CLIENT", "LIST", cmd_command("TYPE", TYPE, FALSE), id)) }, CLIENT_GETNAME = function() { command(list("CLIENT", "GETNAME")) }, - CLIENT_PAUSE = function(timeout) { + CLIENT_GETREDIR = function() { + stop("Do not use CLIENT GETREDIR; not supported with this client") + }, + CLIENT_UNPAUSE = function() { + command(list("CLIENT", "UNPAUSE")) + }, + CLIENT_PAUSE = function(timeout, mode = NULL) { assert_scalar2(timeout) - command(list("CLIENT", "PAUSE", timeout)) + assert_match_value_or_null(mode, c("WRITE", "ALL")) + command(list("CLIENT", "PAUSE", timeout, mode)) }, CLIENT_REPLY = function(reply_mode) { - stop("Do not use CLIENT_REPLY; not supported with this client") + stop("Do not use CLIENT REPLY; not supported with this client") }, CLIENT_SETNAME = function(connection_name) { assert_scalar2(connection_name) command(list("CLIENT", "SETNAME", connection_name)) }, + CLIENT_TRACKING = function(status, REDIRECT = NULL, PREFIX = NULL, BCAST = NULL, OPTIN = NULL, OPTOUT = NULL, NOLOOP = NULL) { + stop("Do not use CLIENT TRACKING; not supported with this client") + }, + CLIENT_TRACKINGINFO = function() { + command(list("CLIENT", "TRACKINGINFO")) + }, + CLIENT_UNBLOCK = function(client_id, unblock_type = NULL) { + assert_scalar2(client_id) + assert_match_value_or_null(unblock_type, c("TIMEOUT", "ERROR")) + command(list("CLIENT", "UNBLOCK", client_id, unblock_type)) + }, CLUSTER_ADDSLOTS = function(slot) { command(list("CLUSTER", "ADDSLOTS", slot)) }, + CLUSTER_BUMPEPOCH = function() { + command(list("CLUSTER", "BUMPEPOCH")) + }, CLUSTER_COUNT_FAILURE_REPORTS = function(node_id) { assert_scalar2(node_id) command(list("CLUSTER", "COUNT-FAILURE-REPORTS", node_id)) @@ -98,6 +191,9 @@ redis_commands <- function(command) { assert_match_value_or_null(options, c("FORCE", "TAKEOVER")) command(list("CLUSTER", "FAILOVER", options)) }, + CLUSTER_FLUSHSLOTS = function() { + command(list("CLUSTER", "FLUSHSLOTS")) + }, CLUSTER_FORGET = function(node_id) { assert_scalar2(node_id) command(list("CLUSTER", "FORGET", node_id)) @@ -119,6 +215,9 @@ redis_commands <- function(command) { assert_scalar2(port) command(list("CLUSTER", "MEET", ip, port)) }, + CLUSTER_MYID = function() { + command(list("CLUSTER", "MYID")) + }, CLUSTER_NODES = function() { command(list("CLUSTER", "NODES")) }, @@ -147,6 +246,10 @@ redis_commands <- function(command) { assert_scalar2(node_id) command(list("CLUSTER", "SLAVES", node_id)) }, + CLUSTER_REPLICAS = function(node_id) { + assert_scalar2(node_id) + command(list("CLUSTER", "REPLICAS", node_id)) + }, CLUSTER_SLOTS = function() { command(list("CLUSTER", "SLOTS")) }, @@ -177,6 +280,13 @@ redis_commands <- function(command) { CONFIG_RESETSTAT = function() { command(list("CONFIG", "RESETSTAT")) }, + COPY = function(source, destination, DB = NULL, replace = NULL) { + assert_scalar2(source) + assert_scalar2(destination) + assert_scalar_or_null2(DB) + assert_match_value_or_null(replace, c("REPLACE")) + command(list("COPY", source, destination, cmd_command("DB", DB, FALSE), replace)) + }, DBSIZE = function() { command(list("DBSIZE")) }, @@ -215,11 +325,21 @@ redis_commands <- function(command) { assert_scalar2(numkeys) command(list("EVAL", script, numkeys, key, arg)) }, + EVAL_RO = function(script, numkeys, key, arg) { + assert_scalar2(script) + assert_scalar2(numkeys) + command(list("EVAL_RO", script, numkeys, key, arg)) + }, EVALSHA = function(sha1, numkeys, key, arg) { assert_scalar2(sha1) assert_scalar2(numkeys) command(list("EVALSHA", sha1, numkeys, key, arg)) }, + EVALSHA_RO = function(sha1, numkeys, key, arg) { + assert_scalar2(sha1) + assert_scalar2(numkeys) + command(list("EVALSHA_RO", sha1, numkeys, key, arg)) + }, EXEC = function() { command(list("EXEC")) }, @@ -236,16 +356,30 @@ redis_commands <- function(command) { assert_scalar2(timestamp) command(list("EXPIREAT", key, timestamp)) }, - FLUSHALL = function() { - command(list("FLUSHALL")) + EXPIRETIME = function(key) { + assert_scalar2(key) + command(list("EXPIRETIME", key)) + }, + FAILOVER = function(target = NULL, ABORT = NULL, TIMEOUT = NULL) { + assert_scalar_or_null2(target) + assert_scalar_or_null2(ABORT) + assert_scalar_or_null2(TIMEOUT) + command(list("FAILOVER", target, cmd_command("ABORT", ABORT, FALSE), cmd_command("TIMEOUT", TIMEOUT, FALSE))) + }, + FLUSHALL = function(async = NULL) { + assert_match_value_or_null(async, c("ASYNC", "SYNC")) + command(list("FLUSHALL", async)) }, - FLUSHDB = function() { - command(list("FLUSHDB")) + FLUSHDB = function(async = NULL) { + assert_match_value_or_null(async, c("ASYNC", "SYNC")) + command(list("FLUSHDB", async)) }, - GEOADD = function(key, longitude, latitude, member) { + GEOADD = function(key, longitude, latitude, member, condition = NULL, change = NULL) { assert_scalar2(key) + assert_match_value_or_null(condition, c("NX", "XX")) + assert_match_value_or_null(change, c("CH")) longitude <- cmd_interleave(longitude, latitude, member) - command(list("GEOADD", key, longitude)) + command(list("GEOADD", key, condition, change, longitude)) }, GEOHASH = function(key, member) { assert_scalar2(key) @@ -259,10 +393,10 @@ redis_commands <- function(command) { assert_scalar2(key) assert_scalar2(member1) assert_scalar2(member2) - assert_scalar_or_null2(unit) + assert_match_value_or_null(unit, c("m", "km", "ft", "mi")) command(list("GEODIST", key, member1, member2, unit)) }, - GEORADIUS = function(key, longitude, latitude, radius, unit, withcoord = NULL, withdist = NULL, withhash = NULL, COUNT = NULL, order = NULL, STORE = NULL, STOREDIST = NULL) { + GEORADIUS = function(key, longitude, latitude, radius, unit, withcoord = NULL, withdist = NULL, withhash = NULL, count = NULL, order = NULL, STORE = NULL, STOREDIST = NULL) { assert_scalar2(key) assert_scalar2(longitude) assert_scalar2(latitude) @@ -271,13 +405,13 @@ redis_commands <- function(command) { assert_match_value_or_null(withcoord, c("WITHCOORD")) assert_match_value_or_null(withdist, c("WITHDIST")) assert_match_value_or_null(withhash, c("WITHHASH")) - assert_scalar_or_null2(COUNT) + assert_scalar_or_null2(count) assert_match_value_or_null(order, c("ASC", "DESC")) assert_scalar_or_null2(STORE) assert_scalar_or_null2(STOREDIST) - command(list("GEORADIUS", key, longitude, latitude, radius, unit, withcoord, withdist, withhash, cmd_command("COUNT", COUNT, FALSE), order, cmd_command("STORE", STORE, FALSE), cmd_command("STOREDIST", STOREDIST, FALSE))) + command(list("GEORADIUS", key, longitude, latitude, radius, unit, withcoord, withdist, withhash, count, order, cmd_command("STORE", STORE, FALSE), cmd_command("STOREDIST", STOREDIST, FALSE))) }, - GEORADIUSBYMEMBER = function(key, member, radius, unit, withcoord = NULL, withdist = NULL, withhash = NULL, COUNT = NULL, order = NULL, STORE = NULL, STOREDIST = NULL) { + GEORADIUSBYMEMBER = function(key, member, radius, unit, withcoord = NULL, withdist = NULL, withhash = NULL, count = NULL, order = NULL, STORE = NULL, STOREDIST = NULL) { assert_scalar2(key) assert_scalar2(member) assert_scalar2(radius) @@ -285,11 +419,36 @@ redis_commands <- function(command) { assert_match_value_or_null(withcoord, c("WITHCOORD")) assert_match_value_or_null(withdist, c("WITHDIST")) assert_match_value_or_null(withhash, c("WITHHASH")) - assert_scalar_or_null2(COUNT) + assert_scalar_or_null2(count) assert_match_value_or_null(order, c("ASC", "DESC")) assert_scalar_or_null2(STORE) assert_scalar_or_null2(STOREDIST) - command(list("GEORADIUSBYMEMBER", key, member, radius, unit, withcoord, withdist, withhash, cmd_command("COUNT", COUNT, FALSE), order, cmd_command("STORE", STORE, FALSE), cmd_command("STOREDIST", STOREDIST, FALSE))) + command(list("GEORADIUSBYMEMBER", key, member, radius, unit, withcoord, withdist, withhash, count, order, cmd_command("STORE", STORE, FALSE), cmd_command("STOREDIST", STOREDIST, FALSE))) + }, + GEOSEARCH = function(key, FROMMEMBER = NULL, FROMLONLAT = NULL, circle = NULL, box = NULL, order = NULL, count = NULL, withcoord = NULL, withdist = NULL, withhash = NULL) { + assert_scalar2(key) + assert_scalar_or_null2(FROMMEMBER) + assert_length_or_null(FROMLONLAT, 2L) + assert_scalar_or_null2(circle) + assert_scalar_or_null2(box) + assert_match_value_or_null(order, c("ASC", "DESC")) + assert_scalar_or_null2(count) + assert_match_value_or_null(withcoord, c("WITHCOORD")) + assert_match_value_or_null(withdist, c("WITHDIST")) + assert_match_value_or_null(withhash, c("WITHHASH")) + command(list("GEOSEARCH", key, cmd_command("FROMMEMBER", FROMMEMBER, FALSE), cmd_command("FROMLONLAT", FROMLONLAT, TRUE), circle, box, order, count, withcoord, withdist, withhash)) + }, + GEOSEARCHSTORE = function(destination, source, FROMMEMBER = NULL, FROMLONLAT = NULL, circle = NULL, box = NULL, order = NULL, count = NULL, storedist = NULL) { + assert_scalar2(destination) + assert_scalar2(source) + assert_scalar_or_null2(FROMMEMBER) + assert_length_or_null(FROMLONLAT, 2L) + assert_scalar_or_null2(circle) + assert_scalar_or_null2(box) + assert_match_value_or_null(order, c("ASC", "DESC")) + assert_scalar_or_null2(count) + assert_match_value_or_null(storedist, c("STOREDIST")) + command(list("GEOSEARCHSTORE", destination, source, cmd_command("FROMMEMBER", FROMMEMBER, FALSE), cmd_command("FROMLONLAT", FROMLONLAT, TRUE), circle, box, order, count, storedist)) }, GET = function(key) { assert_scalar2(key) @@ -300,6 +459,15 @@ redis_commands <- function(command) { assert_scalar2(offset) command(list("GETBIT", key, offset)) }, + GETDEL = function(key) { + assert_scalar2(key) + command(list("GETDEL", key)) + }, + GETEX = function(key, expiration = NULL) { + assert_scalar2(key) + assert_match_value_or_null(expiration, c("EX seconds", "PX milliseconds", "EXAT timestamp", "PXAT milliseconds-timestamp", "PERSIST")) + command(list("GETEX", key, expiration)) + }, GETRANGE = function(key, start, end) { assert_scalar2(key) assert_scalar2(start) @@ -315,6 +483,9 @@ redis_commands <- function(command) { assert_scalar2(key) command(list("HDEL", key, field)) }, + HELLO = function(arguments = NULL) { + stop("Do not use HELLO; RESP3 not supported with this client") + }, HEXISTS = function(key, field) { assert_scalar2(key) assert_scalar2(field) @@ -360,9 +531,8 @@ redis_commands <- function(command) { }, HSET = function(key, field, value) { assert_scalar2(key) - assert_scalar2(field) - assert_scalar2(value) - command(list("HSET", key, field, value)) + field <- cmd_interleave(field, value) + command(list("HSET", key, field)) }, HSETNX = function(key, field, value) { assert_scalar2(key) @@ -370,6 +540,11 @@ redis_commands <- function(command) { assert_scalar2(value) command(list("HSETNX", key, field, value)) }, + HRANDFIELD = function(key, options = NULL) { + assert_scalar2(key) + assert_scalar_or_null2(options) + command(list("HRANDFIELD", key, options)) + }, HSTRLEN = function(key, field) { assert_scalar2(key) assert_scalar2(field) @@ -397,6 +572,12 @@ redis_commands <- function(command) { assert_scalar_or_null2(section) command(list("INFO", section)) }, + LOLWUT = function(VERSION = NULL) { + assert_scalar_or_null2(VERSION) + res <- command(list("LOLWUT", cmd_command("VERSION", VERSION, FALSE))) + message(trimws(res)) + invisible(res) + }, KEYS = function(pattern) { assert_scalar2(pattern) command(list("KEYS", pattern)) @@ -409,29 +590,37 @@ redis_commands <- function(command) { assert_scalar2(index) command(list("LINDEX", key, index)) }, - LINSERT = function(key, where, pivot, value) { + LINSERT = function(key, where, pivot, element) { assert_scalar2(key) assert_match_value(where, c("BEFORE", "AFTER")) assert_scalar2(pivot) - assert_scalar2(value) - command(list("LINSERT", key, where, pivot, value)) + assert_scalar2(element) + command(list("LINSERT", key, where, pivot, element)) }, LLEN = function(key) { assert_scalar2(key) command(list("LLEN", key)) }, - LPOP = function(key) { + LPOP = function(key, count = NULL) { assert_scalar2(key) - command(list("LPOP", key)) + assert_scalar_or_null2(count) + command(list("LPOP", key, count)) }, - LPUSH = function(key, value) { + LPOS = function(key, element, RANK = NULL, COUNT = NULL, MAXLEN = NULL) { assert_scalar2(key) - command(list("LPUSH", key, value)) + assert_scalar2(element) + assert_scalar_or_null2(RANK) + assert_scalar_or_null2(COUNT) + assert_scalar_or_null2(MAXLEN) + command(list("LPOS", key, element, cmd_command("RANK", RANK, FALSE), cmd_command("COUNT", COUNT, FALSE), cmd_command("MAXLEN", MAXLEN, FALSE))) }, - LPUSHX = function(key, value) { + LPUSH = function(key, element) { assert_scalar2(key) - assert_scalar2(value) - command(list("LPUSHX", key, value)) + command(list("LPUSH", key, element)) + }, + LPUSHX = function(key, element) { + assert_scalar2(key) + command(list("LPUSHX", key, element)) }, LRANGE = function(key, start, stop) { assert_scalar2(key) @@ -439,17 +628,17 @@ redis_commands <- function(command) { assert_scalar2(stop) command(list("LRANGE", key, start, stop)) }, - LREM = function(key, count, value) { + LREM = function(key, count, element) { assert_scalar2(key) assert_scalar2(count) - assert_scalar2(value) - command(list("LREM", key, count, value)) + assert_scalar2(element) + command(list("LREM", key, count, element)) }, - LSET = function(key, index, value) { + LSET = function(key, index, element) { assert_scalar2(key) assert_scalar2(index) - assert_scalar2(value) - command(list("LSET", key, index, value)) + assert_scalar2(element) + command(list("LSET", key, index, element)) }, LTRIM = function(key, start, stop) { assert_scalar2(key) @@ -457,10 +646,30 @@ redis_commands <- function(command) { assert_scalar2(stop) command(list("LTRIM", key, start, stop)) }, + MEMORY_DOCTOR = function() { + command(list("MEMORY", "DOCTOR")) + }, + MEMORY_HELP = function() { + command(list("MEMORY", "HELP")) + }, + MEMORY_MALLOC_STATS = function() { + command(list("MEMORY", "MALLOC-STATS")) + }, + MEMORY_PURGE = function() { + command(list("MEMORY", "PURGE")) + }, + MEMORY_STATS = function() { + command(list("MEMORY", "STATS")) + }, + MEMORY_USAGE = function(key, SAMPLES = NULL) { + assert_scalar2(key) + assert_scalar_or_null2(SAMPLES) + command(list("MEMORY", "USAGE", key, cmd_command("SAMPLES", SAMPLES, FALSE))) + }, MGET = function(key) { command(list("MGET", key)) }, - MIGRATE = function(host, port, key, destination_db, timeout, copy = NULL, replace = NULL, KEYS = NULL) { + MIGRATE = function(host, port, key, destination_db, timeout, copy = NULL, replace = NULL, AUTH = NULL, AUTH2 = NULL, KEYS = NULL) { assert_scalar2(host) assert_scalar2(port) assert_match_value(key, c("key", "")) @@ -468,7 +677,20 @@ redis_commands <- function(command) { assert_scalar2(timeout) assert_match_value_or_null(copy, c("COPY")) assert_match_value_or_null(replace, c("REPLACE")) - command(list("MIGRATE", host, port, key, destination_db, timeout, copy, replace, cmd_command("KEYS", KEYS, TRUE))) + assert_scalar_or_null2(AUTH) + assert_scalar_or_null2(AUTH2) + command(list("MIGRATE", host, port, key, destination_db, timeout, copy, replace, cmd_command("AUTH", AUTH, FALSE), cmd_command("AUTH2", AUTH2, FALSE), cmd_command("KEYS", KEYS, TRUE))) + }, + MODULE_LIST = function() { + command(list("MODULE", "LIST")) + }, + MODULE_LOAD = function(path, arg = NULL) { + assert_scalar2(path) + command(list("MODULE", "LOAD", path, arg)) + }, + MODULE_UNLOAD = function(name) { + assert_scalar2(name) + command(list("MODULE", "UNLOAD", name)) }, MONITOR = function() { command(list("MONITOR")) @@ -507,6 +729,10 @@ redis_commands <- function(command) { assert_scalar2(milliseconds_timestamp) command(list("PEXPIREAT", key, milliseconds_timestamp)) }, + PEXPIRETIME = function(key) { + assert_scalar2(key) + command(list("PEXPIRETIME", key)) + }, PFADD = function(key, element) { assert_scalar2(key) command(list("PFADD", key, element)) @@ -569,33 +795,46 @@ redis_commands <- function(command) { assert_scalar2(newkey) command(list("RENAMENX", key, newkey)) }, - RESTORE = function(key, ttl, serialized_value, replace = NULL) { + RESET = function() { + command(list("RESET")) + }, + RESTORE = function(key, ttl, serialized_value, replace = NULL, absttl = NULL, IDLETIME = NULL, FREQ = NULL) { assert_scalar2(key) assert_scalar2(ttl) assert_scalar2(serialized_value) assert_match_value_or_null(replace, c("REPLACE")) - command(list("RESTORE", key, ttl, serialized_value, replace)) + assert_match_value_or_null(absttl, c("ABSTTL")) + assert_scalar_or_null2(IDLETIME) + assert_scalar_or_null2(FREQ) + command(list("RESTORE", key, ttl, serialized_value, replace, absttl, cmd_command("IDLETIME", IDLETIME, FALSE), cmd_command("FREQ", FREQ, FALSE))) }, ROLE = function() { command(list("ROLE")) }, - RPOP = function(key) { + RPOP = function(key, count = NULL) { assert_scalar2(key) - command(list("RPOP", key)) + assert_scalar_or_null2(count) + command(list("RPOP", key, count)) }, RPOPLPUSH = function(source, destination) { assert_scalar2(source) assert_scalar2(destination) command(list("RPOPLPUSH", source, destination)) }, - RPUSH = function(key, value) { + LMOVE = function(source, destination, wherefrom, whereto) { + assert_scalar2(source) + assert_scalar2(destination) + assert_match_value(wherefrom, c("LEFT", "RIGHT")) + assert_match_value(whereto, c("LEFT", "RIGHT")) + command(list("LMOVE", source, destination, wherefrom, whereto)) + }, + RPUSH = function(key, element) { assert_scalar2(key) - command(list("RPUSH", key, value)) + command(list("RPUSH", key, element)) }, - RPUSHX = function(key, value) { + RPUSHX = function(key, element) { assert_scalar2(key) - assert_scalar2(value) - command(list("RPUSHX", key, value)) + command(list("RPUSHX", key, element)) }, SADD = function(key, member) { assert_scalar2(key) @@ -615,8 +854,9 @@ redis_commands <- function(command) { SCRIPT_EXISTS = function(sha1) { command(list("SCRIPT", "EXISTS", sha1)) }, - SCRIPT_FLUSH = function() { - command(list("SCRIPT", "FLUSH")) + SCRIPT_FLUSH = function(async = NULL) { + assert_match_value_or_null(async, c("ASYNC", "SYNC")) + command(list("SCRIPT", "FLUSH", async)) }, SCRIPT_KILL = function() { command(list("SCRIPT", "KILL")) @@ -636,13 +876,13 @@ redis_commands <- function(command) { assert_scalar2(index) command(list("SELECT", index)) }, - SET = function(key, value, EX = NULL, PX = NULL, condition = NULL) { + SET = function(key, value, expiration = NULL, condition = NULL, get = NULL) { assert_scalar2(key) assert_scalar2(value) - assert_scalar_or_null2(EX) - assert_scalar_or_null2(PX) + assert_match_value_or_null(expiration, c("EX seconds", "PX milliseconds", "EXAT timestamp", "PXAT milliseconds-timestamp", "KEEPTTL")) assert_match_value_or_null(condition, c("NX", "XX")) - command(list("SET", key, value, cmd_command("EX", EX, FALSE), cmd_command("PX", PX, FALSE), condition)) + assert_match_value_or_null(get, c("GET")) + command(list("SET", key, value, expiration, condition, get)) }, SETBIT = function(key, offset, value) { assert_scalar2(key) @@ -683,11 +923,20 @@ redis_commands <- function(command) { assert_scalar2(member) command(list("SISMEMBER", key, member)) }, + SMISMEMBER = function(key, member) { + assert_scalar2(key) + command(list("SMISMEMBER", key, member)) + }, SLAVEOF = function(host, port) { assert_scalar2(host) assert_scalar2(port) command(list("SLAVEOF", host, port)) }, + REPLICAOF = function(host, port) { + assert_scalar2(host) + assert_scalar2(port) + command(list("REPLICAOF", host, port)) + }, SLOWLOG = function(subcommand, argument = NULL) { assert_scalar2(subcommand) assert_scalar_or_null2(argument) @@ -726,6 +975,10 @@ redis_commands <- function(command) { assert_scalar2(key) command(list("SREM", key, member)) }, + STRALGO = function(algorithm, algo_specific_argument) { + assert_match_value(algorithm, c("LCS")) + command(list("STRALGO", algorithm, algo_specific_argument)) + }, STRLEN = function(key) { assert_scalar2(key) command(list("STRLEN", key)) @@ -740,9 +993,17 @@ redis_commands <- function(command) { assert_scalar2(destination) command(list("SUNIONSTORE", destination, key)) }, + SWAPDB = function(index1, index2) { + assert_scalar2(index1) + assert_scalar2(index2) + command(list("SWAPDB", index1, index2)) + }, SYNC = function() { command(list("SYNC")) }, + PSYNC = function(replicationid, offset) { + stop("Do not use PSYNC; not supported with this client") + }, TIME = function() { command(list("TIME")) }, @@ -766,21 +1027,22 @@ redis_commands <- function(command) { UNWATCH = function() { command(list("UNWATCH")) }, - WAIT = function(numslaves, timeout) { - assert_scalar2(numslaves) + WAIT = function(numreplicas, timeout) { + assert_scalar2(numreplicas) assert_scalar2(timeout) - command(list("WAIT", numslaves, timeout)) + command(list("WAIT", numreplicas, timeout)) }, WATCH = function(key) { command(list("WATCH", key)) }, - ZADD = function(key, score, member, condition = NULL, change = NULL, increment = NULL) { + ZADD = function(key, score, member, condition = NULL, comparison = NULL, change = NULL, increment = NULL) { assert_scalar2(key) assert_match_value_or_null(condition, c("NX", "XX")) + assert_match_value_or_null(comparison, c("GT", "LT")) assert_match_value_or_null(change, c("CH")) assert_match_value_or_null(increment, c("INCR")) score <- cmd_interleave(score, member) - command(list("ZADD", key, condition, change, increment, score)) + command(list("ZADD", key, condition, comparison, change, increment, score)) }, ZCARD = function(key) { assert_scalar2(key) @@ -792,12 +1054,28 @@ redis_commands <- function(command) { assert_scalar2(max) command(list("ZCOUNT", key, min, max)) }, + ZDIFF = function(numkeys, key, withscores = NULL) { + assert_scalar2(numkeys) + assert_match_value_or_null(withscores, c("WITHSCORES")) + command(list("ZDIFF", numkeys, key, withscores)) + }, + ZDIFFSTORE = function(destination, numkeys, key) { + assert_scalar2(destination) + assert_scalar2(numkeys) + command(list("ZDIFFSTORE", destination, numkeys, key)) + }, ZINCRBY = function(key, increment, member) { assert_scalar2(key) assert_scalar2(increment) assert_scalar2(member) command(list("ZINCRBY", key, increment, member)) }, + ZINTER = function(numkeys, key, WEIGHTS = NULL, AGGREGATE = NULL, withscores = NULL) { + assert_scalar2(numkeys) + assert_match_value_or_null(AGGREGATE, c("SUM", "MIN", "MAX")) + assert_match_value_or_null(withscores, c("WITHSCORES")) + command(list("ZINTER", numkeys, key, cmd_command("WEIGHTS", WEIGHTS, TRUE), cmd_command("AGGREGATE", AGGREGATE, FALSE), withscores)) + }, ZINTERSTORE = function(destination, numkeys, key, WEIGHTS = NULL, AGGREGATE = NULL) { assert_scalar2(destination) assert_scalar2(numkeys) @@ -810,12 +1088,40 @@ redis_commands <- function(command) { assert_scalar2(max) command(list("ZLEXCOUNT", key, min, max)) }, - ZRANGE = function(key, start, stop, withscores = NULL) { + ZPOPMAX = function(key, count = NULL) { assert_scalar2(key) - assert_scalar2(start) - assert_scalar2(stop) + assert_scalar_or_null2(count) + command(list("ZPOPMAX", key, count)) + }, + ZPOPMIN = function(key, count = NULL) { + assert_scalar2(key) + assert_scalar_or_null2(count) + command(list("ZPOPMIN", key, count)) + }, + ZRANDMEMBER = function(key, options = NULL) { + assert_scalar2(key) + assert_scalar_or_null2(options) + command(list("ZRANDMEMBER", key, options)) + }, + ZRANGESTORE = function(dst, src, min, max, sortby = NULL, rev = NULL, LIMIT = NULL) { + assert_scalar2(dst) + assert_scalar2(src) + assert_scalar2(min) + assert_scalar2(max) + assert_match_value_or_null(sortby, c("BYSCORE", "BYLEX")) + assert_match_value_or_null(rev, c("REV")) + assert_length_or_null(LIMIT, 2L) + command(list("ZRANGESTORE", dst, src, min, max, sortby, rev, cmd_command("LIMIT", LIMIT, TRUE))) + }, + ZRANGE = function(key, min, max, sortby = NULL, rev = NULL, LIMIT = NULL, withscores = NULL) { + assert_scalar2(key) + assert_scalar2(min) + assert_scalar2(max) + assert_match_value_or_null(sortby, c("BYSCORE", "BYLEX")) + assert_match_value_or_null(rev, c("REV")) + assert_length_or_null(LIMIT, 2L) assert_match_value_or_null(withscores, c("WITHSCORES")) - command(list("ZRANGE", key, start, stop, withscores)) + command(list("ZRANGE", key, min, max, sortby, rev, cmd_command("LIMIT", LIMIT, TRUE), withscores)) }, ZRANGEBYLEX = function(key, min, max, LIMIT = NULL) { assert_scalar2(key) @@ -891,17 +1197,28 @@ redis_commands <- function(command) { assert_scalar2(member) command(list("ZSCORE", key, member)) }, + ZUNION = function(numkeys, key, WEIGHTS = NULL, AGGREGATE = NULL, withscores = NULL) { + assert_scalar2(numkeys) + assert_match_value_or_null(AGGREGATE, c("SUM", "MIN", "MAX")) + assert_match_value_or_null(withscores, c("WITHSCORES")) + command(list("ZUNION", numkeys, key, cmd_command("WEIGHTS", WEIGHTS, TRUE), cmd_command("AGGREGATE", AGGREGATE, FALSE), withscores)) + }, + ZMSCORE = function(key, member) { + assert_scalar2(key) + command(list("ZMSCORE", key, member)) + }, ZUNIONSTORE = function(destination, numkeys, key, WEIGHTS = NULL, AGGREGATE = NULL) { assert_scalar2(destination) assert_scalar2(numkeys) assert_match_value_or_null(AGGREGATE, c("SUM", "MIN", "MAX")) command(list("ZUNIONSTORE", destination, numkeys, key, cmd_command("WEIGHTS", WEIGHTS, TRUE), cmd_command("AGGREGATE", AGGREGATE, FALSE))) }, - SCAN = function(cursor, MATCH = NULL, COUNT = NULL) { + SCAN = function(cursor, MATCH = NULL, COUNT = NULL, TYPE = NULL) { assert_scalar2(cursor) assert_scalar_or_null2(MATCH) assert_scalar_or_null2(COUNT) - command(list("SCAN", cursor, cmd_command("MATCH", MATCH, FALSE), cmd_command("COUNT", COUNT, FALSE))) + assert_scalar_or_null2(TYPE) + command(list("SCAN", cursor, cmd_command("MATCH", MATCH, FALSE), cmd_command("COUNT", COUNT, FALSE), cmd_command("TYPE", TYPE, FALSE))) }, SSCAN = function(key, cursor, MATCH = NULL, COUNT = NULL) { assert_scalar2(key) @@ -923,9 +1240,137 @@ redis_commands <- function(command) { assert_scalar_or_null2(MATCH) assert_scalar_or_null2(COUNT) command(list("ZSCAN", key, cursor, cmd_command("MATCH", MATCH, FALSE), cmd_command("COUNT", COUNT, FALSE))) + }, + XINFO = function(CONSUMERS = NULL, GROUPS = NULL, STREAM = NULL, help = NULL) { + assert_length_or_null(CONSUMERS, 2L) + assert_scalar_or_null2(GROUPS) + assert_scalar_or_null2(STREAM) + assert_match_value_or_null(help, c("HELP")) + command(list("XINFO", cmd_command("CONSUMERS", CONSUMERS, TRUE), cmd_command("GROUPS", GROUPS, FALSE), cmd_command("STREAM", STREAM, FALSE), help)) + }, + XADD = function(key, field, value, NOMKSTREAM = NULL, trim = NULL) { + assert_scalar2(key) + assert_scalar_or_null2(NOMKSTREAM) + assert_scalar_or_null2(trim) + field <- cmd_interleave(field, value) + command(list("XADD", key, cmd_command("NOMKSTREAM", NOMKSTREAM, FALSE), trim, field, NA)) + }, + XTRIM = function(key, trim) { + assert_scalar2(key) + assert_scalar2(trim) + command(list("XTRIM", key, trim)) + }, + XDEL = function(key, ID) { + assert_scalar2(key) + command(list("XDEL", key, ID)) + }, + XRANGE = function(key, start, end, COUNT = NULL) { + assert_scalar2(key) + assert_scalar2(start) + assert_scalar2(end) + assert_scalar_or_null2(COUNT) + command(list("XRANGE", key, start, end, cmd_command("COUNT", COUNT, FALSE))) + }, + XREVRANGE = function(key, end, start, COUNT = NULL) { + assert_scalar2(key) + assert_scalar2(end) + assert_scalar2(start) + assert_scalar_or_null2(COUNT) + command(list("XREVRANGE", key, end, start, cmd_command("COUNT", COUNT, FALSE))) + }, + XLEN = function(key) { + assert_scalar2(key) + command(list("XLEN", key)) + }, + XREAD = function(streams, key, ID, COUNT = NULL, BLOCK = NULL) { + assert_scalar_or_null2(COUNT) + assert_scalar_or_null2(BLOCK) + assert_match_value(streams, c("STREAMS")) + command(list("XREAD", cmd_command("COUNT", COUNT, FALSE), cmd_command("BLOCK", BLOCK, FALSE), streams, key, ID)) + }, + XGROUP = function(create = NULL, setid = NULL, DESTROY = NULL, CREATECONSUMER = NULL, DELCONSUMER = NULL) { + assert_scalar_or_null2(create) + assert_scalar_or_null2(setid) + assert_length_or_null(DESTROY, 2L) + assert_length_or_null(CREATECONSUMER, 3L) + assert_length_or_null(DELCONSUMER, 3L) + command(list("XGROUP", create, setid, cmd_command("DESTROY", DESTROY, TRUE), cmd_command("CREATECONSUMER", CREATECONSUMER, TRUE), cmd_command("DELCONSUMER", DELCONSUMER, TRUE))) + }, + XREADGROUP = function(GROUP, streams, key, ID, COUNT = NULL, BLOCK = NULL, noack = NULL) { + assert_length(GROUP, 2L) + assert_scalar_or_null2(COUNT) + assert_scalar_or_null2(BLOCK) + assert_match_value_or_null(noack, c("NOACK")) + assert_match_value(streams, c("STREAMS")) + command(list("XREADGROUP", cmd_command("GROUP", GROUP, TRUE), cmd_command("COUNT", COUNT, FALSE), cmd_command("BLOCK", BLOCK, FALSE), noack, streams, key, ID)) + }, + XACK = function(key, group, ID) { + assert_scalar2(key) + assert_scalar2(group) + command(list("XACK", key, group, ID)) + }, + XCLAIM = function(key, group, consumer, min_idle_time, ID, IDLE = NULL, TIME = NULL, RETRYCOUNT = NULL, force = NULL, justid = NULL) { + assert_scalar2(key) + assert_scalar2(group) + assert_scalar2(consumer) + assert_scalar2(min_idle_time) + assert_scalar_or_null2(IDLE) + assert_scalar_or_null2(TIME) + assert_scalar_or_null2(RETRYCOUNT) + assert_match_value_or_null(force, c("FORCE")) + assert_match_value_or_null(justid, c("JUSTID")) + command(list("XCLAIM", key, group, consumer, min_idle_time, ID, cmd_command("IDLE", IDLE, FALSE), cmd_command("TIME", TIME, FALSE), cmd_command("RETRYCOUNT", RETRYCOUNT, FALSE), force, justid)) + }, + XAUTOCLAIM = function(key, group, consumer, min_idle_time, start, COUNT = NULL, justid = NULL) { + assert_scalar2(key) + assert_scalar2(group) + assert_scalar2(consumer) + assert_scalar2(min_idle_time) + assert_scalar2(start) + assert_scalar_or_null2(COUNT) + assert_match_value_or_null(justid, c("JUSTID")) + command(list("XAUTOCLAIM", key, group, consumer, min_idle_time, start, cmd_command("COUNT", COUNT, FALSE), justid)) + }, + XPENDING = function(key, group, filters = NULL) { + assert_scalar2(key) + assert_scalar2(group) + assert_scalar_or_null2(filters) + command(list("XPENDING", key, group, filters)) + }, + LATENCY_DOCTOR = function() { + command(list("LATENCY", "DOCTOR")) + }, + LATENCY_GRAPH = function(event) { + assert_scalar2(event) + command(list("LATENCY", "GRAPH", event)) + }, + LATENCY_HISTORY = function(event) { + assert_scalar2(event) + command(list("LATENCY", "HISTORY", event)) + }, + LATENCY_LATEST = function() { + command(list("LATENCY", "LATEST")) + }, + LATENCY_RESET = function(event = NULL) { + command(list("LATENCY", "RESET", event)) + }, + LATENCY_HELP = function() { + command(list("LATENCY", "HELP")) }) } cmd_since <- numeric_version(c( + ACL_CAT = "6.0.0", + ACL_DELUSER = "6.0.0", + ACL_GENPASS = "6.0.0", + ACL_GETUSER = "6.0.0", + ACL_HELP = "6.0.0", + ACL_LIST = "6.0.0", + ACL_LOAD = "6.0.0", + ACL_LOG = "6.0.0", + ACL_SAVE = "6.0.0", + ACL_SETUSER = "6.0.0", + ACL_USERS = "6.0.0", + ACL_WHOAMI = "6.0.0", APPEND = "2.0.0", AUTH = "1.0.0", BGREWRITEAOF = "1.0.0", @@ -934,26 +1379,41 @@ cmd_since <- numeric_version(c( BITFIELD = "3.2.0", BITOP = "2.6.0", BITPOS = "2.8.7", + BLMOVE = "6.2.0", BLPOP = "2.0.0", BRPOP = "2.0.0", BRPOPLPUSH = "2.2.0", + BZPOPMAX = "5.0.0", + BZPOPMIN = "5.0.0", + CLIENT_CACHING = "6.0.0", CLIENT_GETNAME = "2.6.9", + CLIENT_GETREDIR = "6.0.0", + CLIENT_ID = "5.0.0", + CLIENT_INFO = "6.2.0", CLIENT_KILL = "2.4.0", CLIENT_LIST = "2.4.0", CLIENT_PAUSE = "2.9.50", - CLIENT_REPLY = "3.2", + CLIENT_REPLY = "3.2.0", CLIENT_SETNAME = "2.6.9", + CLIENT_TRACKING = "6.0.0", + CLIENT_TRACKINGINFO = "6.2.0", + CLIENT_UNBLOCK = "5.0.0", + CLIENT_UNPAUSE = "6.2.0", CLUSTER_ADDSLOTS = "3.0.0", + CLUSTER_BUMPEPOCH = "3.0.0", CLUSTER_COUNT_FAILURE_REPORTS = "3.0.0", CLUSTER_COUNTKEYSINSLOT = "3.0.0", CLUSTER_DELSLOTS = "3.0.0", CLUSTER_FAILOVER = "3.0.0", + CLUSTER_FLUSHSLOTS = "3.0.0", CLUSTER_FORGET = "3.0.0", CLUSTER_GETKEYSINSLOT = "3.0.0", CLUSTER_INFO = "3.0.0", CLUSTER_KEYSLOT = "3.0.0", CLUSTER_MEET = "3.0.0", + CLUSTER_MYID = "3.0.0", CLUSTER_NODES = "3.0.0", + CLUSTER_REPLICAS = "5.0.0", CLUSTER_REPLICATE = "3.0.0", CLUSTER_RESET = "3.0.0", CLUSTER_SAVECONFIG = "3.0.0", @@ -969,6 +1429,7 @@ cmd_since <- numeric_version(c( CONFIG_RESETSTAT = "2.0.0", CONFIG_REWRITE = "2.8.0", CONFIG_SET = "2.0.0", + COPY = "6.2.0", DBSIZE = "1.0.0", DEBUG_OBJECT = "1.0.0", DEBUG_SEGFAULT = "1.0.0", @@ -979,11 +1440,15 @@ cmd_since <- numeric_version(c( DUMP = "2.6.0", ECHO = "1.0.0", EVAL = "2.6.0", + EVAL_RO = "7.0.0", EVALSHA = "2.6.0", + EVALSHA_RO = "7.0.0", EXEC = "1.2.0", EXISTS = "1.0.0", EXPIRE = "1.0.0", EXPIREAT = "1.2.0", + EXPIRETIME = "7.0.0", + FAILOVER = "6.2.0", FLUSHALL = "1.0.0", FLUSHDB = "1.0.0", GEOADD = "3.2.0", @@ -992,11 +1457,16 @@ cmd_since <- numeric_version(c( GEOPOS = "3.2.0", GEORADIUS = "3.2.0", GEORADIUSBYMEMBER = "3.2.0", + GEOSEARCH = "6.2", + GEOSEARCHSTORE = "6.2", GET = "1.0.0", GETBIT = "2.2.0", + GETDEL = "6.2.0", + GETEX = "6.2.0", GETRANGE = "2.4.0", GETSET = "1.0.0", HDEL = "2.0.0", + HELLO = "6.0.0", HEXISTS = "2.0.0", HGET = "2.0.0", HGETALL = "2.0.0", @@ -1006,6 +1476,7 @@ cmd_since <- numeric_version(c( HLEN = "2.0.0", HMGET = "2.0.0", HMSET = "2.0.0", + HRANDFIELD = "6.2.0", HSCAN = "2.8.0", HSET = "2.0.0", HSETNX = "2.0.0", @@ -1017,18 +1488,36 @@ cmd_since <- numeric_version(c( INFO = "1.0.0", KEYS = "1.0.0", LASTSAVE = "1.0.0", + LATENCY_DOCTOR = "2.8.13", + LATENCY_GRAPH = "2.8.13", + LATENCY_HELP = "2.8.13", + LATENCY_HISTORY = "2.8.13", + LATENCY_LATEST = "2.8.13", + LATENCY_RESET = "2.8.13", LINDEX = "1.0.0", LINSERT = "2.2.0", LLEN = "1.0.0", + LMOVE = "6.2.0", + LOLWUT = "5.0.0", LPOP = "1.0.0", + LPOS = "6.0.6", LPUSH = "1.0.0", LPUSHX = "2.2.0", LRANGE = "1.0.0", LREM = "1.0.0", LSET = "1.0.0", LTRIM = "1.0.0", + MEMORY_DOCTOR = "4.0.0", + MEMORY_HELP = "4.0.0", + MEMORY_MALLOC_STATS = "4.0.0", + MEMORY_PURGE = "4.0.0", + MEMORY_STATS = "4.0.0", + MEMORY_USAGE = "4.0.0", MGET = "1.0.0", MIGRATE = "2.6.0", + MODULE_LIST = "4.0.0", + MODULE_LOAD = "4.0.0", + MODULE_UNLOAD = "4.0.0", MONITOR = "1.0.0", MOVE = "1.0.0", MSET = "1.0.1", @@ -1038,12 +1527,14 @@ cmd_since <- numeric_version(c( PERSIST = "2.2.0", PEXPIRE = "2.6.0", PEXPIREAT = "2.6.0", + PEXPIRETIME = "7.0.0", PFADD = "2.8.9", PFCOUNT = "2.8.9", PFMERGE = "2.8.9", PING = "1.0.0", PSETEX = "2.6.0", PSUBSCRIBE = "2.0.0", + PSYNC = "2.8.0", PTTL = "2.6.0", PUBLISH = "2.0.0", PUBSUB = "2.8.0", @@ -1054,6 +1545,8 @@ cmd_since <- numeric_version(c( READWRITE = "3.0.0", RENAME = "1.0.0", RENAMENX = "1.0.0", + REPLICAOF = "5.0.0", + RESET = "6.2", RESTORE = "2.6.0", ROLE = "2.8.12", RPOP = "1.0.0", @@ -1084,16 +1577,19 @@ cmd_since <- numeric_version(c( SLAVEOF = "1.0.0", SLOWLOG = "2.2.12", SMEMBERS = "1.0.0", + SMISMEMBER = "6.2.0", SMOVE = "1.0.0", SORT = "1.0.0", SPOP = "1.0.0", SRANDMEMBER = "1.0.0", SREM = "1.0.0", SSCAN = "2.8.0", + STRALGO = "6.0.0", STRLEN = "2.2.0", SUBSCRIBE = "2.0.0", SUNION = "1.0.0", SUNIONSTORE = "1.0.0", + SWAPDB = "4.0.0", SYNC = "1.0.0", TIME = "2.6.0", TOUCH = "3.2.1", @@ -1104,15 +1600,37 @@ cmd_since <- numeric_version(c( UNWATCH = "2.2.0", WAIT = "3.0.0", WATCH = "2.2.0", + XACK = "5.0.0", + XADD = "5.0.0", + XAUTOCLAIM = "6.2.0", + XCLAIM = "5.0.0", + XDEL = "5.0.0", + XGROUP = "5.0.0", + XINFO = "5.0.0", + XLEN = "5.0.0", + XPENDING = "5.0.0", + XRANGE = "5.0.0", + XREAD = "5.0.0", + XREADGROUP = "5.0.0", + XREVRANGE = "5.0.0", + XTRIM = "5.0.0", ZADD = "1.2.0", ZCARD = "1.2.0", ZCOUNT = "2.0.0", + ZDIFF = "6.2.0", + ZDIFFSTORE = "6.2.0", ZINCRBY = "1.2.0", + ZINTER = "6.2.0", ZINTERSTORE = "2.0.0", ZLEXCOUNT = "2.8.9", + ZMSCORE = "6.2.0", + ZPOPMAX = "5.0.0", + ZPOPMIN = "5.0.0", + ZRANDMEMBER = "6.2.0", ZRANGE = "1.2.0", ZRANGEBYLEX = "2.8.9", ZRANGEBYSCORE = "1.0.5", + ZRANGESTORE = "6.2.0", ZRANK = "2.0.0", ZREM = "1.2.0", ZREMRANGEBYLEX = "2.8.9", @@ -1124,4 +1642,5 @@ cmd_since <- numeric_version(c( ZREVRANK = "2.0.0", ZSCAN = "2.8.0", ZSCORE = "1.2.0", + ZUNION = "6.2.0", ZUNIONSTORE = "2.0.0")) diff --git a/R/util_assert.R b/R/util_assert.R index a59785a..1c299a1 100644 --- a/R/util_assert.R +++ b/R/util_assert.R @@ -1,5 +1,5 @@ assert_match_value <- function(x, choices, name = deparse(substitute(x))) { - assert_scalar_character(x) + assert_scalar_character(x, name = name) if (!(x %in% choices)) { stop(sprintf("%s must be one of %s", name, paste(dQuote(choices), collapse = ", "))) diff --git a/configure b/configure index 7885df8..4793254 100755 --- a/configure +++ b/configure @@ -15,9 +15,10 @@ PKG_CFLAGS="-I/usr/include/hiredis" PKG_LIBS="-lhiredis" # Use pkg-config if available -if [ $(command -v pkg-config) ]; then - PKGCONFIG_CFLAGS=$(pkg-config --cflags --silence-errors ${PKG_CONFIG_NAME}) - PKGCONFIG_LIBS=$(pkg-config --libs ${PKG_CONFIG_NAME}) +pkg-config ${PKG_CONFIG_NAME} --atleast-version=0.19 2>/dev/null +if [ $? -eq 0 ]; then + PKGCONFIG_CFLAGS=`pkg-config --cflags --silence-errors ${PKG_CONFIG_NAME}` + PKGCONFIG_LIBS=`pkg-config --libs ${PKG_CONFIG_NAME}` fi # Note that cflags may be empty in case of success @@ -29,15 +30,15 @@ elif [ "$PKGCONFIG_CFLAGS" ] || [ "$PKGCONFIG_LIBS" ]; then echo "Found pkg-config cflags and libs!" PKG_CFLAGS="${PKGCONFIG_CFLAGS} ${PKG_CFLAGS}" PKG_LIBS=${PKGCONFIG_LIBS} -elif [[ "$OSTYPE" == "darwin"* ]]; then - if [ $(command -v brew) ]; then - BREWDIR=$(brew --prefix) - PKG_CFLAGS="-I$BREWDIR/include/hiredis -D_FILE_OFFSET_BITS=64" - PKG_LIBS="-L$BREWDIR/lib $PKG_LIBS" +elif [ `uname` = "Darwin" ]; then + brew --version 2>/dev/null + if [ $? -eq 0 ]; then + BREWDIR=`brew --prefix` + PKG_CFLAGS="-I$BREWDIR/opt/$PKG_BREW_NAME/include" + PKG_LIBS="-L$BREWDIR/opt/$PKG_BREW_NAME/lib $PKG_LIBS" else - curl -sfL "https://autobrew.github.io/scripts/hiredis" > autobrew - source autobrew - rm autobrew + curl -sfL "https://autobrew.github.io/scripts/$PKG_BREW_NAME" > autobrew + . autobrew fi fi diff --git a/extra/generate_fun.R b/extra/generate_fun.R index 5c70427..bc19615 100644 --- a/extra/generate_fun.R +++ b/extra/generate_fun.R @@ -68,13 +68,21 @@ hiredis_cmd <- function(x, standalone = FALSE) { is_grouped <- lengths(args$name) > 1L if (any(is_command)) { + if (is.null(args$name)) { + args$name <- args$command + } + j <- is_command & !(is_grouped & is_multiple) args$name_orig <- args$name args$command_length <- viapply(args$name, length) args$name[j] <- args$command[j] is_grouped <- viapply(args$name, length) > 1L } - + + if (any(duplicated(args$name))) { + stop("Duplicated names") + } + if (any(args$variadic)) { args$command_length[args$variadic] <- 2 } @@ -115,9 +123,10 @@ hiredis_cmd <- function(x, standalone = FALSE) { stop("duplicate names") } ## The colon here is for CLIENT KILL - args$name <- gsub("[-:]", "_", args$name) + ## The space here is for count or RESET + args$name <- gsub("[-: ]", "_", args$name) if (any(grepl("[^A-Za-z0-9._]", args$name))) { - stop("invalid names") + stop("invalid names: ", paste(args$name, collapse = ", ")) } is_optional <- is_field("optional", args) @@ -194,9 +203,33 @@ hiredis_cmd <- function(x, standalone = FALSE) { run <- sprintf( 'stop("Do not use %s(); see subscribe() instead (lower-case)")', name) } - if (name == "CLIENT REPLY") { + if (name %in% c("CLIENT REPLY", "PSYNC")) { check <- group <- NULL - run <- 'stop("Do not use CLIENT_REPLY; not supported with this client")' + run <- sprintf('stop("Do not use %s; not supported with this client")', + name) + } + if (name %in% paste("CLIENT", c("CACHING", "GETREDIR", "TRACKING"))) { + ## Don't allow use of any client-side caching related functions as + ## they do nothing useful. + check <- group <- NULL + run <- sprintf('stop("Do not use %s; not supported with this client")', + name) + } + if (name == "HELLO") { + ## TODO: we *could* support this + ## https://github.com/redis/hiredis/issues/648 + ## + ## though there would be other changes required for the package in + ## terms of the types of responses that we get back: + ## * check hiredis library version before using + ## * high-level connection interface should support this + check <- group <- NULL + run <- 'stop("Do not use HELLO; RESP3 not supported with this client")' + } + if (name == "LOLWUT") { + run <- c(sprintf("res <- %s", run), + "message(trimws(res))", + "invisible(res)") } fn_body <- paste(indent(c(check, group, run)), collapse = "\n") diff --git a/src/conversions.c b/src/conversions.c index c0df922..00084c1 100644 --- a/src/conversions.c +++ b/src/conversions.c @@ -60,6 +60,7 @@ SEXP redis_check_command(SEXP cmd) { // and FALSE -> "0", rather than to "TRUE"/"FALSE". el = PROTECT(coerceVector(el, INTSXP)); np++; + // fall through case INTSXP: case REALSXP: el = PROTECT(coerceVector(el, STRSXP)); diff --git a/tests/testthat/helper-common.R b/tests/testthat/helper-common.R index 06156ce..2c669c6 100644 --- a/tests/testthat/helper-common.R +++ b/tests/testthat/helper-common.R @@ -53,10 +53,6 @@ rand_str <- function(len = 8, prefix = "") { paste(sample(c(LETTERS, letters, 0:9), len), collapse = "")) } -vcapply <- function(X, FUN, ...) { - vapply(X, FUN, character(1), ...) -} - sys_setenv <- function(...) { vars <- names(list(...)) prev <- vapply(vars, Sys.getenv, "", NA_character_) diff --git a/tests/testthat/test-zzz-commands-acl.R b/tests/testthat/test-zzz-commands-acl.R new file mode 100644 index 0000000..fc5fe07 --- /dev/null +++ b/tests/testthat/test-zzz-commands-acl.R @@ -0,0 +1,129 @@ +context("commands - acl") + +test_that("ACL CAT", { + expect_equal(redis_cmds$ACL_CAT(), list("ACL", "CAT", NULL)) + expect_equal(redis_cmds$ACL_CAT("read"), list("ACL", "CAT", "read")) +}) + +test_that("ACL CAT (server)", { + skip_if_cmd_unsupported("ACL_CAT") + con <- test_hiredis_connection() + res <- con$ACL_CAT() + expect_type(res, "list") + expect_true("dangerous" %in% unlist(res)) + res <- con$ACL_CAT("dangerous") + expect_type(res, "list") + expect_true("acl" %in% unlist(res)) +}) + + +test_that("ACL DEL", { + expect_equal(redis_cmds$ACL_DELUSER("x"), list("ACL", "DELUSER", "x")) + expect_equal(redis_cmds$ACL_DELUSER(c("x", "y")), + list("ACL", "DELUSER", c("x", "y"))) +}) + + +test_that("ACL GENPASS", { + expect_equal(redis_cmds$ACL_GENPASS(), list("ACL", "GENPASS", NULL)) + expect_equal(redis_cmds$ACL_GENPASS(256), list("ACL", "GENPASS", 256)) +}) + + +test_that("ACL GENPASS (server)", { + skip_if_cmd_unsupported("ACL_GENPASS") + con <- test_hiredis_connection() + expect_match(con$ACL_GENPASS(), "^[[:xdigit:]]{64}$") + expect_match(con$ACL_GENPASS(128), "^[[:xdigit:]]{32}$") +}) + + +test_that("ACL GETUSER", { + expect_equal(redis_cmds$ACL_GETUSER("default"), + list("ACL", "GETUSER", "default")) +}) + + +test_that("ACL GETUSER (server)", { + skip_if_cmd_unsupported("ACL_GETUSER") + con <- test_hiredis_connection() + res <- con$ACL_GETUSER("default") + expect_type(res, "list") +}) + + +test_that("ACL HELP", { + expect_equal(redis_cmds$ACL_HELP(), list("ACL", "HELP")) +}) + + +## This is dubiously helpful in its current form +test_that("ACL HELP (server)", { + skip_if_cmd_unsupported("ACL_HELP") + con <- test_hiredis_connection() + res <- con$ACL_HELP() + expect_type(res, "list") +}) + + +test_that("ACL LIST", { + expect_equal(redis_cmds$ACL_LIST(), list("ACL", "LIST")) +}) + + +test_that("ACL LIST", { + skip_if_cmd_unsupported("ACL_LIST") + con <- test_hiredis_connection() + res <- con$ACL_LIST() + expect_match(unlist(res), "^user default", all = FALSE) +}) + + +test_that("ACL LOAD", { + expect_equal(redis_cmds$ACL_LOAD(), list("ACL", "LOAD")) +}) + + +test_that("ACL LOG", { + expect_equal(redis_cmds$ACL_LOG(), list("ACL", "LOG", NULL)) + expect_equal(redis_cmds$ACL_LOG("RESET"), list("ACL", "LOG", "RESET")) + expect_equal(redis_cmds$ACL_LOG(1), list("ACL", "LOG", 1)) +}) + + +test_that("ACL SAVE", { + expect_equal(redis_cmds$ACL_SAVE(), list("ACL", "SAVE")) +}) + + +test_that("ACL SETUSER", { + expect_equal(redis_cmds$ACL_SETUSER("me"), list("ACL", "SETUSER", "me", NULL)) + expect_equal(redis_cmds$ACL_SETUSER("me", c("reset", "rule", "value")), + list("ACL", "SETUSER", "me", c("reset", "rule", "value"))) +}) + + + +test_that("ACL USERS", { + expect_equal(redis_cmds$ACL_USERS(), list("ACL", "USERS")) +}) + + +test_that("ACL USERS (server)", { + skip_if_cmd_unsupported("ACL_USERS") + con <- test_hiredis_connection() + res <- con$ACL_USERS() + expect_true("default" %in% unlist(res)) +}) + + +test_that("ACL WHOAMI", { + expect_equal(redis_cmds$ACL_WHOAMI(), list("ACL", "WHOAMI")) +}) + + +test_that("ACL WHOAMI (server)", { + skip_if_cmd_unsupported("ACL_WHOAMI") + con <- test_hiredis_connection() + expect_equal(con$ACL_WHOAMI(), "default") +}) diff --git a/tests/testthat/test-zzz-commands-cluster.R b/tests/testthat/test-zzz-commands-cluster.R index 4e9ae33..bddf9e0 100644 --- a/tests/testthat/test-zzz-commands-cluster.R +++ b/tests/testthat/test-zzz-commands-cluster.R @@ -5,6 +5,11 @@ test_that("CLUSTER ADDSLOTS", { list("CLUSTER", "ADDSLOTS", 1:3)) }) +test_that("CLUSTER BUMPEPOCH", { + expect_equal(redis_cmds$CLUSTER_BUMPEPOCH(), + list("CLUSTER", "BUMPEPOCH")) +}) + test_that("CLUSTER COUNT-FAILURE-REPORTS", { expect_equal(redis_cmds$CLUSTER_COUNT_FAILURE_REPORTS("id"), list("CLUSTER", "COUNT-FAILURE-REPORTS", "id")) @@ -29,6 +34,11 @@ test_that("CLUSTER FAILOVER", { list("CLUSTER", "FAILOVER", "TAKEOVER")) }) +test_that("CLUSTER FLUSHSLOTS", { + expect_equal(redis_cmds$CLUSTER_FLUSHSLOTS(), + list("CLUSTER", "FLUSHSLOTS")) +}) + test_that("CLUSTER FORGET", { expect_equal(redis_cmds$CLUSTER_FORGET("id"), list("CLUSTER", "FORGET", "id")) @@ -54,11 +64,21 @@ test_that("CLUSTER MEET", { list("CLUSTER", "MEET", "B-ip", "B-port")) }) +test_that("CLUSTER MYID", { + expect_equal(redis_cmds$CLUSTER_MYID(), + list("CLUSTER", "MYID")) +}) + test_that("CLUSTER NODES", { expect_equal(redis_cmds$CLUSTER_NODES(), list("CLUSTER", "NODES")) }) +test_that("CLUSTER REPLICAS", { + expect_equal(redis_cmds$CLUSTER_REPLICAS("id"), + list("CLUSTER", "REPLICAS", "id")) +}) + test_that("CLUSTER REPLICATE", { expect_equal(redis_cmds$CLUSTER_REPLICATE("id"), list("CLUSTER", "REPLICATE", "id")) diff --git a/tests/testthat/test-zzz-commands-connection.R b/tests/testthat/test-zzz-commands-connection.R index 72cfde1..107be69 100644 --- a/tests/testthat/test-zzz-commands-connection.R +++ b/tests/testthat/test-zzz-commands-connection.R @@ -2,7 +2,8 @@ context("commands - connection") test_that("AUTH", { pw <- rand_str() - expect_equal(redis_cmds$AUTH(pw), list("AUTH", pw)) + expect_equal(redis_cmds$AUTH(pw), list("AUTH", NULL, pw)) + expect_equal(redis_cmds$AUTH(pw, "user"), list("AUTH", "user", pw)) }) test_that("ECHO", { diff --git a/tests/testthat/test-zzz-commands-generic.R b/tests/testthat/test-zzz-commands-generic.R index 810b302..64205a2 100644 --- a/tests/testthat/test-zzz-commands-generic.R +++ b/tests/testthat/test-zzz-commands-generic.R @@ -287,7 +287,8 @@ test_that("MIGRATE", { timeout <- 5000 keys <- letters expect_equal(redis_cmds$MIGRATE(ip, port, "", db, timeout, KEYS = letters), - list("MIGRATE", ip, port, "", db, timeout, NULL, NULL, + list("MIGRATE", ip, port, "", db, timeout, + NULL, NULL, NULL, NULL, list("KEYS", keys))) }) diff --git a/tests/testthat/test-zzz-commands-geo.R b/tests/testthat/test-zzz-commands-geo.R index 650d566..51b92c6 100644 --- a/tests/testthat/test-zzz-commands-geo.R +++ b/tests/testthat/test-zzz-commands-geo.R @@ -6,8 +6,8 @@ test_that("GEOADD:prep", { y <- c(38.115556, 37.502669) nms <- c("Palermo", "Catania") - expect_equal(redis_cmds$GEOADD(key, x, y, nms), - list("GEOADD", key, c(rbind(x, y, nms)))) + expect_equal(unlist(redis_cmds$GEOADD(key, x, y, nms)), + c("GEOADD", key, rbind(x, y, nms))) }) test_that("GEOADD:run", { @@ -29,8 +29,8 @@ test_that("GEOADD:run", { test_that("GEOHASH:prep", { key <- rand_str() nms <- c("Palermo", "Catania") - list(redis_cmds$GEOHASH(key, nms), - list("GEOHASH", key, nms)) + expect_equal(redis_cmds$GEOHASH(key, nms), + list("GEOHASH", key, nms)) }) test_that("GEOHASH:run", { diff --git a/tests/testthat/test-zzz-commands-list.R b/tests/testthat/test-zzz-commands-list.R index 15b1bff..7dc088e 100644 --- a/tests/testthat/test-zzz-commands-list.R +++ b/tests/testthat/test-zzz-commands-list.R @@ -39,6 +39,27 @@ test_that("BRPOPLPUSH", { expect_equal(con$LRANGE(key2, 0, -1), list("three")) }) +test_that("BLMOVE (mock)", { + expect_equal( + redis_cmds$BLMOVE("key1", "key2", "RIGHT", "LEFT", 100), + list("BLMOVE", "key1", "key2", "RIGHT", "LEFT", 100)) +}) + +test_that("BLMOVE", { + skip_if_cmd_unsupported("BLMOVE") + con <- test_hiredis_connection(version = TRUE) + key1 <- rand_str() + key2 <- rand_str() + on.exit(con$DEL(c(key1, key2))) + + con$RPUSH(key1, "one") + con$RPUSH(key1, "two") + con$RPUSH(key1, "three") + expect_equal(con$BLMOVE(key1, key2, "RIGHT", "LEFT", 100), "three") + expect_equal(con$LRANGE(key1, 0, -1), list("one", "two")) + expect_equal(con$LRANGE(key2, 0, -1), list("three")) +}) + test_that("LINDEX", { skip_if_cmd_unsupported("LINDEX") con <- test_hiredis_connection() @@ -89,6 +110,35 @@ test_that("LPOP", { expect_equal(con$LRANGE(key, 0, -1), list("two", "three")) }) +test_that("LPOS (mock)", { + expect_equal( + redis_cmds$LPOS("key", "c"), + list("LPOS", "key", "c", NULL, NULL, NULL)) + expect_equal( + redis_cmds$LPOS("key", "c", RANK = 2), + list("LPOS", "key", "c", list("RANK", 2), NULL, NULL)) + expect_equal( + redis_cmds$LPOS("key", "c", RANK = 2, COUNT = 2), + list("LPOS", "key", "c", list("RANK", 2), list("COUNT", 2), NULL)) + expect_equal( + redis_cmds$LPOS("key", "c", RANK = 2, COUNT = 2), + list("LPOS", "key", "c", list("RANK", 2), list("COUNT", 2), NULL)) +}) + +test_that("LPOS", { + skip_if_cmd_unsupported("LPOS") + con <- test_hiredis_connection() + key <- rand_str() + con$RPUSH(key, c("a", "b", "c", 1, 2, 3, "c", "c")) + on.exit(con$DEL(key)) + expect_equal(con$LPOS(key, "c"), 2L) + expect_equal(con$LPOS(key, "c", RANK = 2), 6L) + expect_equal(con$LPOS(key, "c", RANK = -1), 7L) + expect_equal(con$LPOS(key, "c", COUNT = 2), list(2L, 6L)) + expect_equal(con$LPOS(key, "c", RANK = -1, COUNT = 2), list(7L, 6L)) + expect_equal(con$LPOS(key, "c", COUNT = 0), list(2L, 6L, 7L)) +}) + test_that("LPUSH", { skip_if_cmd_unsupported("LPUSH") con <- test_hiredis_connection() @@ -222,3 +272,24 @@ test_that("RPUSHX", { expect_equal(con$LRANGE(key1, 0, -1), list("hello", "world")) expect_equal(con$LRANGE(key2, 0, -1), list()) }) + +test_that("LMOVE (mock)", { + expect_equal( + redis_cmds$LMOVE("key1", "key2", "RIGHT", "LEFT"), + list("LMOVE", "key1", "key2", "RIGHT", "LEFT")) +}) + +test_that("LMOVE", { + skip_if_cmd_unsupported("LMOVE") + con <- test_hiredis_connection(version = TRUE) + key1 <- rand_str() + key2 <- rand_str() + on.exit(con$DEL(c(key1, key2))) + + con$RPUSH(key1, "one") + con$RPUSH(key1, "two") + con$RPUSH(key1, "three") + expect_equal(con$LMOVE(key1, key2, "RIGHT", "LEFT"), "three") + expect_equal(con$LRANGE(key1, 0, -1), list("one", "two")) + expect_equal(con$LRANGE(key2, 0, -1), list("three")) +}) diff --git a/tests/testthat/test-zzz-commands-server.R b/tests/testthat/test-zzz-commands-server.R index 9dd1dc9..4b9e4d9 100644 --- a/tests/testthat/test-zzz-commands-server.R +++ b/tests/testthat/test-zzz-commands-server.R @@ -2,16 +2,21 @@ context("commands - server") ## Tested on the server test_that("CLIENT KILL", { - expect_equal(redis_cmds$CLIENT_KILL(ID = "12", SKIPME = "yes"), - list("CLIENT", "KILL", NULL, list("ID", "12"), - NULL, NULL, list("SKIPME", "yes"))) - expect_equal(redis_cmds$CLIENT_KILL(ID = "11", SKIPME = "no"), - list("CLIENT", "KILL", NULL, list("ID", "11"), - NULL, NULL, list("SKIPME", "no"))) + expect_equal( + unlist(redis_cmds$CLIENT_KILL(ID = "12", SKIPME = "yes")), + c("CLIENT", "KILL", "ID", "12", "SKIPME", "yes")) + expect_equal( + unlist(redis_cmds$CLIENT_KILL(ID = "11", SKIPME = "no")), + c("CLIENT", "KILL", "ID", "11", "SKIPME", "no")) }) test_that("CLIENT LIST", { - expect_equal(redis_cmds$CLIENT_LIST(), list("CLIENT", "LIST")) + expect_equal( + unlist(redis_cmds$CLIENT_LIST()), + c("CLIENT", "LIST")) + expect_equal( + unlist(redis_cmds$CLIENT_LIST("normal")), + c("CLIENT", "LIST", "TYPE", "normal")) }) test_that("CLIENT GETNAME", { @@ -19,13 +24,14 @@ test_that("CLIENT GETNAME", { }) test_that("CLIENT PAUSE", { - expect_equal(redis_cmds$CLIENT_PAUSE(1000), - list("CLIENT", "PAUSE", 1000)) + expect_equal( + unlist(redis_cmds$CLIENT_PAUSE(1000)), + c("CLIENT", "PAUSE", 1000)) }) test_that("CLIENT REPLY", { expect_error(redis_cmds$CLIENT_REPLY("SKIP"), - "Do not use CLIENT_REPLY") + "Do not use CLIENT REPLY") }) test_that("CLIENT SETNAME", { @@ -34,6 +40,40 @@ test_that("CLIENT SETNAME", { list("CLIENT", "SETNAME", name)) }) +## New client commands since version 5-6 +test_that("CLIENT ID", { + expect_equal(redis_cmds$CLIENT_ID(), list("CLIENT", "ID")) +}) + +test_that("CLIENT ID (server)", { + skip_if_cmd_unsupported("CLIENT_ID") + con <- test_hiredis_connection() + res <- con$CLIENT_ID() + expect_type(res, "integer") +}) + +test_that("CLIENT CACHING", { + expect_error(redis_cmds$CLIENT_CACHING("YES"), + "Do not use CLIENT CACHING; not supported with this client") +}) + +test_that("CLIENT GETREDIR", { + expect_error(redis_cmds$CLIENT_GETREDIR(), + "Do not use CLIENT GETREDIR; not supported with this client") +}) + +test_that("CLIENT TRACKING", { + expect_error(redis_cmds$CLIENT_TRACKING("ON"), + "Do not use CLIENT TRACKING; not supported with this client") +}) + +test_that("CLIENT UNBLOCK", { + expect_equal(redis_cmds$CLIENT_UNBLOCK(1L), + list("CLIENT", "UNBLOCK", 1L, NULL)) + expect_equal(redis_cmds$CLIENT_UNBLOCK(1L, "ERROR"), + list("CLIENT", "UNBLOCK", 1L, "ERROR")) +}) + test_that("COMMAND", { expect_equal(redis_cmds$COMMAND(), list("COMMAND")) }) @@ -66,11 +106,19 @@ test_that("DBSIZE", { }) test_that("FLUSHALL", { - expect_equal(redis_cmds$FLUSHALL(), list("FLUSHALL")) + expect_equal(unlist(redis_cmds$FLUSHALL()), + "FLUSHALL") + expect_equal(unlist(redis_cmds$FLUSHALL("ASYNC")), + c("FLUSHALL", "ASYNC")) + expect_error(redis_cmds$FLUSHALL("other"), + "async must be one of") }) test_that("FLUSHDB", { - expect_equal(redis_cmds$FLUSHDB(), list("FLUSHDB")) + expect_equal(redis_cmds$FLUSHDB(), list("FLUSHDB", NULL)) + expect_equal(redis_cmds$FLUSHDB("ASYNC"), list("FLUSHDB", "ASYNC")) + expect_error(redis_cmds$FLUSHDB("other"), + "async must be one of") }) test_that("INFO", { @@ -102,7 +150,8 @@ test_that("BGREWRITEAOF", { }) test_that("BGSAVE", { - expect_equal(redis_cmds$BGSAVE(), list("BGSAVE")) + expect_equal(redis_cmds$BGSAVE(), list("BGSAVE", NULL)) + expect_equal(redis_cmds$BGSAVE("SCHEDULE"), list("BGSAVE", "SCHEDULE")) }) test_that("CONFIG REWRITE", { @@ -150,6 +199,110 @@ test_that("SLAVEOF", { expect_equal(redis_cmds$SLAVEOF("NO", "ONE"), list("SLAVEOF", "NO", "ONE")) }) +test_that("REPLICAOF", { + expect_equal(redis_cmds$REPLICAOF("NO", "ONE"), + list("REPLICAOF", "NO", "ONE")) +}) + test_that("SYNC", { expect_equal(redis_cmds$SYNC(), list("SYNC")) }) + +test_that("LOLWUT", { + expect_equal(redis_cmds$LOLWUT(), list("LOLWUT", NULL)) + expect_equal(redis_cmds$LOLWUT(5), list("LOLWUT", list("VERSION", 5))) +}) + +test_that("LOLWUT (server)", { + skip_if_cmd_unsupported("LOLWUT") + con <- test_hiredis_connection() + expect_message(con$LOLWUT()) +}) + +test_that("MEMORY DOCTOR", { + expect_equal(redis_cmds$MEMORY_DOCTOR(), list("MEMORY", "DOCTOR")) +}) + +test_that("MEMORY HELP", { + expect_equal(redis_cmds$MEMORY_HELP(), list("MEMORY", "HELP")) +}) + +test_that("MEMORY MALLOC_STATS", { + expect_equal(redis_cmds$MEMORY_MALLOC_STATS(), + list("MEMORY", "MALLOC-STATS")) +}) + +test_that("MEMORY PURGE", { + expect_equal(redis_cmds$MEMORY_PURGE(), list("MEMORY", "PURGE")) +}) + +test_that("MEMORY PURGE", { + expect_equal(redis_cmds$MEMORY_PURGE(), list("MEMORY", "PURGE")) +}) + +test_that("MEMORY STATS", { + expect_equal(redis_cmds$MEMORY_STATS(), list("MEMORY", "STATS")) +}) + +test_that("MEMORY USAGE", { + expect_equal(redis_cmds$MEMORY_USAGE(""), list("MEMORY", "USAGE", "", NULL)) + expect_equal(redis_cmds$MEMORY_USAGE("key"), + list("MEMORY", "USAGE", "key", NULL)) +}) + +test_that("MODULE LIST", { + expect_equal(redis_cmds$MODULE_LIST(), list("MODULE", "LIST")) +}) + +test_that("MODULE LOAD", { + expect_equal(redis_cmds$MODULE_LOAD("path"), + list("MODULE", "LOAD", "path", NULL)) +}) + +test_that("MODULE UNLOAD", { + expect_equal(redis_cmds$MODULE_UNLOAD("name"), + list("MODULE", "UNLOAD", "name")) +}) + +test_that("SWAP DB", { + expect_equal(redis_cmds$SWAPDB(0, 1), + list("SWAPDB", 0, 1)) +}) + +test_that("LATENCY_DOCTOR", { + expect_equal(redis_cmds$LATENCY_DOCTOR(), list("LATENCY", "DOCTOR")) +}) + + +test_that("LATENCY_GRAPH", { + expect_equal(redis_cmds$LATENCY_GRAPH("command"), + list("LATENCY", "GRAPH", "command")) +}) + +test_that("LATENCY_HISTORY", { + expect_equal(redis_cmds$LATENCY_HISTORY("command"), + list("LATENCY", "HISTORY", "command")) +}) + +test_that("LATENCY_LATEST", { + expect_equal(redis_cmds$LATENCY_LATEST(), list("LATENCY", "LATEST")) +}) + +test_that("LATENCY_RESET", { + expect_equal(redis_cmds$LATENCY_RESET("command"), + list("LATENCY", "RESET", "command")) +}) + +test_that("LATENCY_HELP", { + expect_equal(redis_cmds$LATENCY_HELP(), list("LATENCY", "HELP")) +}) + +test_that("HELLO", { + expect_error(redis_cmds$HELLO(), + "Do not use HELLO; RESP3 not supported with this client") +}) + +test_that("PSYNC", { + expect_error(redis_cmds$PSYNC(), + "Do not use PSYNC; not supported with this client") +}) diff --git a/tests/testthat/test-zzz-commands-set.R b/tests/testthat/test-zzz-commands-set.R index 6896340..1430a0b 100644 --- a/tests/testthat/test-zzz-commands-set.R +++ b/tests/testthat/test-zzz-commands-set.R @@ -225,3 +225,19 @@ test_that("SUNIONSTORE", { }) ## SSCAN in the scan tests + +test_that("SMISMEMBER (mock)", { + expect_equal(redis_cmds$SMISMEMBER("key", c("one", "two")), + list("SMISMEMBER", "key", c("one", "two"))) +}) + +test_that("SMISMEMBER", { + skip_if_cmd_unsupported("SMISMEMBER") + con <- test_hiredis_connection() + key <- rand_str() + on.exit(con$DEL(key)) + + con$SADD(key, "one") + con$SADD(key, "two") + expect_equal(con$SMISMEMBER(key, c("one", "notamember")), list(1, 0)) +}) diff --git a/tests/testthat/test-zzz-commands-sorted-set.R b/tests/testthat/test-zzz-commands-sorted-set.R index 49cf79a..59dc7a0 100644 --- a/tests/testthat/test-zzz-commands-sorted-set.R +++ b/tests/testthat/test-zzz-commands-sorted-set.R @@ -11,7 +11,7 @@ test_that("ZADD", { expect_equal(con$ZADD(key, 1, "one"), 1) expect_equal(con$ZADD(key, 1, "uno"), 1) expect_equal(con$ZADD(key, c(2, 3), c("two", "three")), 2) - expect_equal(con$ZRANGE(key, 0, -1, "WITHSCORES"), + expect_equal(con$ZRANGE(key, 0, -1, withscores = "WITHSCORES"), list("one", "1", "uno", "1", "two", "2", "three", "3")) }) @@ -68,7 +68,7 @@ test_that("ZINCRBY", { ## * ZRANGEBYSCORE: withscores ## * ZREVRANGE: withscores ## * ZREVRANGEBYSCORE: withscores - expect_equal(con$ZRANGE(key, 0, -1, "WITHSCORES"), + expect_equal(con$ZRANGE(key, 0, -1, withscores = "WITHSCORES"), list("two", "2", "one", "3")) }) @@ -86,7 +86,7 @@ test_that("ZINTERSTORE", { con$ZADD(key2, 2, "two") con$ZADD(key2, 3, "three") expect_equal(con$ZINTERSTORE(key3, 2, c(key1, key2), c(2, 3)), 2) - expect_equal(con$ZRANGE(key3, 0, -1, "WITHSCORES"), + expect_equal(con$ZRANGE(key3, 0, -1, withscores = "WITHSCORES"), list("one", "5", "two", "10")) }) @@ -115,7 +115,7 @@ test_that("ZRANGE", { expect_equal(con$ZRANGE(key, 0, -1), list("one", "two", "three")) expect_equal(con$ZRANGE(key, 2, 3), list("three")) expect_equal(con$ZRANGE(key, -2, -1), list("two", "three")) - expect_equal(con$ZRANGE(key, 0, 1, "WITHSCORES"), + expect_equal(con$ZRANGE(key, 0, 1, withscores = "WITHSCORES"), list("one", "1", "two", "2")) }) @@ -194,7 +194,7 @@ test_that("ZREM", { con$ZADD(key, 3, "three") expect_equal(con$ZREM(key, "two"), 1) - expect_equal(con$ZRANGE(key, 0, -1, "WITHSCORES"), + expect_equal(con$ZRANGE(key, 0, -1, withscores = "WITHSCORES"), list("one", "1", "three", "3")) }) @@ -226,7 +226,7 @@ test_that("ZREMRANGEBYRANK", { con$ZADD(key, 3, "three") expect_equal(con$ZREMRANGEBYRANK(key, 0, 1), 2) - expect_equal(con$ZRANGE(key, 0, -1, "WITHSCORES"), + expect_equal(con$ZRANGE(key, 0, -1, withscores = "WITHSCORES"), list("three", "3")) }) @@ -241,7 +241,7 @@ test_that("ZREMRANGEBYSCORE", { con$ZADD(key, 3, "three") expect_equal(con$ZREMRANGEBYSCORE(key, "-inf", "(2"), 1) - expect_equal(con$ZRANGE(key, 0, -1, "WITHSCORES"), + expect_equal(con$ZRANGE(key, 0, -1, withscores = "WITHSCORES"), list("two", "2", "three", "3")) }) @@ -325,6 +325,118 @@ test_that("ZINTERSTORE", { con$ZADD(key2, 3, "three") expect_equal(con$ZUNIONSTORE(key3, 2, c(key1, key2), c(2, 3)), 3) - expect_equal(con$ZRANGE(key3, 0, -1, "WITHSCORES"), + expect_equal(con$ZRANGE(key3, 0, -1, withscores = "WITHSCORES"), list("one", "5", "three", "9", "two", "10")) }) + +test_that("ZINTER (mock)", { + expect_equal( + redis_cmds$ZINTER(2, c("key1", "key2")), + list("ZINTER", 2, c("key1", "key2"), NULL, NULL, NULL)) + expect_equal( + redis_cmds$ZINTER(2, c("key1", "key2"), withscores = "WITHSCORES"), + list("ZINTER", 2, c("key1", "key2"), NULL, NULL, "WITHSCORES")) +}) + +test_that("ZINTER", { + skip_if_cmd_unsupported("ZINTER") + con <- test_hiredis_connection() + key1 <- rand_str() + key2 <- rand_str() + key3 <- rand_str() + on.exit(con$DEL(c(key1, key2, key3))) + + con$ZADD(key1, 1, "one") + con$ZADD(key1, 2, "two") + con$ZADD(key2, 1, "one") + con$ZADD(key2, 2, "two") + con$ZADD(key2, 3, "three") + + expect_equal(con$ZINTER(2, c(key1, key2)), list("one", "two")) + expect_equal(con$ZINTER(2, c(key1, key2), withscores = "WITHSCORES"), + list("one", "2", "two", "4")) +}) + +test_that("ZPOPMIN", { + skip_if_cmd_unsupported("ZPOPMIN") + con <- test_hiredis_connection() + key <- rand_str() + con$ZADD(key, 1, "one") + con$ZADD(key, 2, "two") + con$ZADD(key, 3, "three") + expect_equal(con$ZPOPMIN(key), list("one", "1")) +}) + +test_that("ZPOPMAX", { + skip_if_cmd_unsupported("ZPOPMAX") + con <- test_hiredis_connection() + key <- rand_str() + con$ZADD(key, 1, "one") + con$ZADD(key, 2, "two") + con$ZADD(key, 3, "three") + expect_equal(con$ZPOPMAX(key), list("three", "3")) +}) + +test_that("ZUNION (mock)", { + expect_equal( + redis_cmds$ZUNION(2, c("key1", "key2")), + list("ZUNION", 2, c("key1", "key2"), NULL, NULL, NULL)) + expect_equal( + redis_cmds$ZUNION(2, c("key1", "key2"), withscores = "WITHSCORES"), + list("ZUNION", 2, c("key1", "key2"), NULL, NULL, "WITHSCORES")) +}) + +test_that("ZUNION", { + skip_if_cmd_unsupported("ZUNION") + con <- test_hiredis_connection() + key1 <- rand_str() + key2 <- rand_str() + key3 <- rand_str() + on.exit(con$DEL(c(key1, key2, key3))) + + con$ZADD(key1, 1, "one") + con$ZADD(key1, 2, "two") + con$ZADD(key2, 1, "one") + con$ZADD(key2, 2, "two") + con$ZADD(key2, 3, "three") + + expect_equal(con$ZUNION(2, c(key1, key2)), + list("one", "three", "two")) + expect_equal(con$ZUNION(2, c(key1, key2), withscores = "WITHSCORES"), + list("one", "2", "three", "3", "two", "4")) +}) + +test_that("ZMSCORE (mock)", { + expect_equal(redis_cmds$ZMSCORE("key", c("one", "two", "nofield")), + list("ZMSCORE", "key", c("one", "two", "nofield"))) +}) + +test_that("ZMSCORE", { + skip_if_cmd_unsupported("ZMSCORE") + con <- test_hiredis_connection() + key <- rand_str() + con$ZADD(key, 1, "one") + con$ZADD(key, 2, "two") + expect_equal(con$ZMSCORE(key, c("one", "two", "nofield")), + list("1", "2", NULL)) +}) + +test_that("BZPOPMIN", { + skip_if_cmd_unsupported("BZPOPMIN") + con <- test_hiredis_connection() + key1 <- rand_str() + key2 <- rand_str() + con$ZADD(key1, c(0, 1, 2), c("a", "b", "c")) + expect_equal(con$BZPOPMIN(c(key1, key2), 10), + list(key1, "a", "0")) +}) + +test_that("BZPOPMAX", { + skip_if_cmd_unsupported("BZPOPMAX") + con <- test_hiredis_connection() + key1 <- rand_str() + key2 <- rand_str() + con$ZADD(key1, c(0, 1, 2), c("a", "b", "c")) + expect_equal(con$BZPOPMAX(c(key1, key2), 10), + list(key1, "c", "2")) +}) diff --git a/tests/testthat/test-zzz-commands-stream.R b/tests/testthat/test-zzz-commands-stream.R new file mode 100644 index 0000000..bbfed7b --- /dev/null +++ b/tests/testthat/test-zzz-commands-stream.R @@ -0,0 +1,195 @@ +context("commands (stream)") + +test_that("XADD", { + skip_if_cmd_unsupported("XADD") + con <- test_hiredis_connection() + stream <- rand_str() + id1 <- con$XADD(stream, "*", c("name", "surname"), c("Sara", "OConnor")) + id2 <- con$XADD(stream, "*", paste0("field", 1:3), paste0("value", 1:3)) + expect_equal(con$XLEN(stream), 2) + expect_equal( + con$XRANGE(stream, "-", "+"), + list( + list( + id1, + list("name", "Sara", "surname", "OConnor")), + list( + id2, + list("field1", "value1", "field2", "value2", "field3", "value3")))) +}) + + +test_that("XADD - mock", { + expect_equal( + unlist(redis_cmds$XADD("stream", "*", c("name", "surname"), + c("Sara", "OConnor"))), + c("XADD", "stream", "*", "name", "Sara", "surname", "OConnor")) +}) + + +test_that("XDEL", { + skip_if_cmd_unsupported("XDEL") + con <- test_hiredis_connection() + stream <- rand_str() + id1 <- con$XADD(stream, "*", "a", 1) + id2 <- con$XADD(stream, "*", "b", 2) + id3 <- con$XADD(stream, "*", "c", 3) + con$XDEL(stream, id2) + expect_equal( + con$XRANGE(stream, "-", "+"), + list(list(id1, list("a", "1")), + list(id3, list("c", "3")))) +}) + + +test_that("XDEL - mock", { + expect_equal(unlist(redis_cmds$XDEL("stream", "id")), + c("XDEL", "stream", "id")) +}) + + +test_that("XLEN", { + skip_if_cmd_unsupported("XLEN") + con <- test_hiredis_connection() + stream <- rand_str() + id1 <- con$XADD(stream, "*", "a", 1) + id2 <- con$XADD(stream, "*", "b", 2) + id3 <- con$XADD(stream, "*", "c", 3) + expect_equal(con$XLEN(stream), 3) +}) + + +test_that("XLEN - mock", { + expect_equal(unlist(redis_cmds$XLEN("stream")), + c("XLEN", "stream")) +}) + + +## Adopted from the docs examples +test_that("XACK", { + expect_equal(redis_cmds$XACK("mystream", "mygroup", "1526569495631-0"), + list("XACK", "mystream", "mygroup", "1526569495631-0")) +}) + +test_that("XCLAIM", { + expect_equal( + redis_cmds$XCLAIM("mystream", "mygroup", "Alice", 3600000, + "1526569498055-0"), + list("XCLAIM", "mystream", "mygroup", "Alice", 3600000, "1526569498055-0", + NULL, NULL, NULL, NULL, NULL)) +}) + +## This lot feels like it might want splitting apart? XGROUP_CREATE etc? +test_that("XGROUP", { + expect_equal( + redis_cmds$XGROUP(CREATE = c("mystream", "consumer-group-name", "$")), + list("XGROUP", list("CREATE", c("mystream", "consumer-group-name", "$")), + NULL, NULL, NULL, NULL)) + expect_equal( + redis_cmds$XGROUP(CREATE = c("mystream", "consumer-group-name", 0)), + list("XGROUP", list("CREATE", c("mystream", "consumer-group-name", 0)), + NULL, NULL, NULL, NULL)) + ## TODO: MKSTREAM not supported for CREATE + + expect_equal( + redis_cmds$XGROUP(DESTROY = c("mystream", "consumer-group-name")), + list("XGROUP", NULL, NULL, + list("DESTROY", c("mystream", "consumer-group-name")), NULL, NULL)) + + expect_equal( + redis_cmds$XGROUP(CREATECONSUMER = c("mystream", "consumer-group-name", + "myconsumer123")), + list("XGROUP", NULL, NULL, NULL, + list("CREATECONSUMER", c("mystream", "consumer-group-name", + "myconsumer123")), NULL)) + + expect_equal( + redis_cmds$XGROUP(DELCONSUMER = c("mystream", "consumer-group-name", + "myconsumer123")), + list("XGROUP", NULL, NULL, NULL, NULL, + list("DELCONSUMER", c("mystream", "consumer-group-name", + "myconsumer123")))) + + expect_equal( + redis_cmds$XGROUP(SETID = c("mystream", "consumer-group-name", 0)), + list("XGROUP", NULL, + list("SETID", c("mystream", "consumer-group-name", 0)), + NULL, NULL, NULL)) +}) + + +test_that("XINFO", { + expect_equal( + redis_cmds$XINFO(STREAM = "mystream"), + list("XINFO", NULL, NULL, list("STREAM", "mystream"), NULL)) + expect_equal( + redis_cmds$XINFO(GROUPS = "mystream"), + list("XINFO", NULL, list("GROUPS", "mystream"), NULL, NULL)) + expect_equal( + redis_cmds$XINFO(CONSUMERS = c("mystream", "mygroup")), + list("XINFO", list("CONSUMERS", c("mystream", "mygroup")), + NULL, NULL, NULL)) +}) + + +test_that("XTRIM", { + expect_equal( + unlist(redis_cmds$XTRIM("mystream", "MAXLEN", 1000)), + c("XTRIM", "mystream", "MAXLEN", 1000)) + expect_equal( + unlist(redis_cmds$XTRIM("mystream", "MAXLEN", 1000, "~")), + c("XTRIM", "mystream", "MAXLEN", "~", "1000")) + + ## not yet supported, in 6.2.x only + ## expect_equal( + ## unlist(redis_cmds$XTRIM("mystream", "MINID", 649085820)), + ## c("XTRIM", "mystream", "MINID", 649085820)) +}) + + +test_that("XREVRANGE", { + expect_equal( + unlist(redis_cmds$XREVRANGE("somestream", "+", "-")), + c("XREVRANGE", "somestream", "+", "-")) + expect_equal( + unlist(redis_cmds$XREVRANGE("somestream", "+", "-", COUNT = 1)), + c("XREVRANGE", "somestream", "+", "-", "COUNT", 1)) +}) + + +test_that("XREAD", { + expect_equal( + unlist(redis_cmds$XREAD("STREAMS", "mystream", 0, COUNT = 2)), + c("XREAD", "COUNT", "2", "STREAMS", "mystream", "0")) + expect_equal( + unlist(redis_cmds$XREAD("STREAMS", c("mystream", "writers"), + c("0-0", "0-0"), COUNT = 2)), + c("XREAD", "COUNT", "2", "STREAMS", "mystream", "writers", "0-0", "0-0")) + expect_equal( + unlist(redis_cmds$XREAD("STREAMS", "mystream", "$", COUNT = 100, + BLOCK = 5000)), + c("XREAD", "COUNT", "100", "BLOCK", "5000", "STREAMS", "mystream", "$")) +}) + + +test_that("XREADGROUP", { + expect_equal( + unlist(redis_cmds$XREADGROUP(c("mygroup", "Alice"), "STREAMS", "mystream", + ">", COUNT = 1)), + c("XREADGROUP", "GROUP", "mygroup", "Alice", "COUNT", "1", "STREAMS", + "mystream", ">")) +}) + + +test_that("XPENDING", { + expect_equal( + unlist(redis_cmds$XPENDING("mystream", "group55")), + c("XPENDING", "mystream", "group55")) + expect_equal( + unlist(redis_cmds$XPENDING("mystream", "group55", "-", "+", 10)), + c("XPENDING", "mystream", "group55", "-", "+", 10)) + expect_equal( + unlist(redis_cmds$XPENDING("mystream", "group55", "-", "+", 10, + "consumer-123")), + c("XPENDING", "mystream", "group55", "-", "+", 10, "consumer-123")) +}) diff --git a/tests/testthat/test-zzz-commands-string.R b/tests/testthat/test-zzz-commands-string.R index 56de89a..a1ce680 100644 --- a/tests/testthat/test-zzz-commands-string.R +++ b/tests/testthat/test-zzz-commands-string.R @@ -312,3 +312,17 @@ test_that("STRLEN", { expect_equal(con$STRLEN(key), 11) expect_equal(con$STRLEN("nonexisting"), 0) }) + +test_that("STRALGO (mock)", { + expect_equal( + redis_cmds$STRALGO("LCS", c("STRINGS", "ohmytext", "mynewtext")), + list("STRALGO", "LCS", c("STRINGS", "ohmytext", "mynewtext"))) +}) + +test_that("STRALGO", { + skip_if_cmd_unsupported("STRALGO") + con <- test_hiredis_connection() + expect_equal( + con$STRALGO("LCS", c("STRINGS", "ohmytext", "mynewtext")), + "mytext") +})