diff --git a/README.md b/README.md
index ef6eb84..743ecf7 100644
--- a/README.md
+++ b/README.md
@@ -1478,15 +1478,36 @@ You can start using the cluster right away. You can obtain a connected `Redis` i
// Run your query on using the given Redis key / keys.
RESP *reply = redisxRequest(shard, "GET", key, NULL, NULL, &status);
+ ...
+```
+
+The interactive queries handle both `MOVED` and `ASK` redirections automatically. However, asynchronous queries do not
+since they return before receiving a response. Thus, when using `redisxReadReplyAsync()` later to process replies, you
+should check for redirections:
+
+```c
+ RESP *reply = redisxReadReplyAsync(...);
+
if(redisxClusterMoved(reply)) {
// The key is now served by another shard.
- // You might want to obtain the new shard and try again...
+ // You might want to obtain the new shard and repeat the failed
+ // transaction again (interactively or pipelined)...
+ ...
+ }
+ if(redisxClusterIsMigrating(reply)) {
+ // The key's slot is currently migrating. You may try the redirected
+ // address indicated in the reply, with the ASKING command, e.g. via a
+ // redisxClusterAskMigrating() interactive transaction.
...
}
-
...
+
```
+As a matter a best practice you should never assume that a given keyword is persistently served by the same shard.
+Rather, you should obtain the current shard for the key each time you want to use it with the cluster, and always
+check for errors on shard requests, and repeat failed requests on a newly obtained shard if necessary.
+
Finally, when you are done using the cluster, simply discard it:
```c
@@ -1498,11 +1519,12 @@ Finally, when you are done using the cluster, simply discard it:
### Detecting cluster reconfiguration
In the above example we have shown one way you might check for errors that result from cluster being reconfigured
-on-the-fly, using `redisxClusterMoved()` on the `RESP` reply obtained from the shard.
+on-the-fly, using `redisxClusterMoved()` and/or `redisxClusterIsMigrating()` on the `RESP` reply obtained from the
+shard.
Equivalently, you might use `redisxCheckRESP()` or `redisxCheckDestroyRESP()` also for detecting a cluster
-reconfiguration. Both of these will return a designated `REDIS_MOVED` error code if the keyword is now served on
-another shard:
+reconfiguration. Both of these will return a designated `REDIS_MOVED` or `REDIS_MIGRATING` error code if the keyword
+has moved or is migrating, respectively, to another node, e.g.:
```c
...
@@ -1511,6 +1533,10 @@ another shard:
// The key is now served by another shard.
...
}
+ if(s == REDIS_MIGRATING) {
+ // The key is migrating and may be accessed from new location via an ASKING directive
+ ...
+ }
if(s != X_SUCCESS) {
// The reply is no good for some other reason...
...
@@ -1518,13 +1544,13 @@ another shard:
...
```
-A `REDIS_MOVED` error code will be returned by higher-level functions also, which ingest the `RESP` replies from the
-shard and return a digested error code. For example, `redisxGetStringValue()` will set the output `len` value to
-`REDIS_MOVED` if the value could not be obtained because of a cluster reconfiguration.
+To help manage redirection responses for asynchronous requests, we provide `redisxClusterGetRedirection()` to obtain
+the redirected Redis instance based on the redirection `RESP`. Once the redirected cluster shard is identified you may
+either resubmit the same query as before (e.h. with `redisxSendArrayRequestAsync()`) if `MOVED`, or else repeat the
+query via an interactive `ASKING` directive using `redisxClusterAskMigrating()`.
-As a matter a best practice you should never assume that a given keyword is persistently served by the same shard.
-Rather, you should obtain the current shard for the key each time you want to use it with the cluster, and always
-check for errors on shard requests, and repeat failed requests on a newly obtained shard if necessary.
+A `REDIS_MOVED` error code may be returned by higher-level functions also, which ingest the `RESP` replies from the
+shard and return a digested error code.
diff --git a/include/redisx.h b/include/redisx.h
index a8df7b6..e4a9683 100644
--- a/include/redisx.h
+++ b/include/redisx.h
@@ -129,6 +129,7 @@ enum resp_type {
#define REDIS_UNEXPECTED_RESP (-105) ///< \hideinitializer Got a Redis response of a different type than expected
#define REDIS_UNEXPECTED_ARRAY_SIZE (-106) ///< \hideinitializer Got a Redis response with different number of elements than expected.
#define REDIS_MOVED (-107) ///< \hideinitializer The requested key has moved to another cluster shard.
+#define REDIS_MIGRATING (-108) ///< \hideinitializer The requested key is importing, and you may query with ASKED on the specified node.
/**
* RedisX channel IDs. RedisX uses up to three separate connections to the server: (1) an interactive client, in which
@@ -418,10 +419,14 @@ int redisxSetTLSSkipVerify(Redis *redis, boolean value);
RedisCluster *redisxClusterInit(Redis *node);
Redis *redisxClusterGetShard(RedisCluster *cluster, const char *key);
+boolean redisxClusterIsRedirected(const RESP *reply);
boolean redisxClusterMoved(const RESP *reply);
+boolean redisxClusterIsMigrating(const RESP *reply);
int redisxClusterConnect(RedisCluster *cluster);
int redisxClusterDisconnect(RedisCluster *cluster);
void redisxClusterDestroy(RedisCluster *cluster);
+Redis *redisxClusterGetRedirection(RedisCluster *cluster, const RESP *redirect, boolean refresh);
+RESP *redisxClusterAskMigrating(Redis *redis, const char **args, const int *lengths, int n, int *status);
int redisxPing(Redis *redis, const char *message);
enum redisx_protocol redisxGetProtocol(Redis *redis);
@@ -507,6 +512,7 @@ int redisxUnlockClient(RedisClient *cl);
// Asynchronous access routines (use within redisxLockClient()/ redisxUnlockClient() blocks)...
int redisxSendRequestAsync(RedisClient *cl, const char *command, const char *arg1, const char *arg2, const char *arg3);
int redisxSendArrayRequestAsync(RedisClient *cl, const char **args, const int *length, int n);
+int redisxClusterAskMigratingAsync(RedisClient *cl, const char **args, const int *lengths, int n);
int redisxSetValueAsync(RedisClient *cl, const char *table, const char *key, const char *value, boolean confirm);
int redisxMultiSetAsync(RedisClient *cl, const char *table, const RedisEntry *entries, int n, boolean confirm);
RESP *redisxReadReplyAsync(RedisClient *cl, int *pStatus);
@@ -516,6 +522,7 @@ int redisxIgnoreReplyAsync(RedisClient *cl);
int redisxSkipReplyAsync(RedisClient *cl);
int redisxPublishAsync(Redis *redis, const char *channel, const char *data, int length);
+
// Error generation with stderr message...
int redisxError(const char *func, int errorCode);
const char* redisxErrorDescription(int code);
diff --git a/man/man1/redisx-cli.1.gz b/man/man1/redisx-cli.1.gz
index a52b389..ce13c14 100644
Binary files a/man/man1/redisx-cli.1.gz and b/man/man1/redisx-cli.1.gz differ
diff --git a/src/redisx-cli.c b/src/redisx-cli.c
index 344edbf..95caf09 100644
--- a/src/redisx-cli.c
+++ b/src/redisx-cli.c
@@ -5,6 +5,16 @@
#define _POSIX_C_SOURCE 199309L ///< for nanosleep()
+// We'll use gcc major version as a proxy for the glibc library to decide which feature macro to use.
+// gcc 5.1 was released 2015-04-22...
+#ifndef __GNUC__
+# define _DEFAULT_SOURCE ///< strcasecmp() feature macro starting glibc 2.20 (2014-09-08)
+#elif __GNUC__ >= 5
+# define _DEFAULT_SOURCE ///< strcasecmp() feature macro starting glibc 2.20 (2014-09-08)
+#else
+# define _BSD_SOURCE ///< strcasecmp() feature macro for glibc <= 2.19
+#endif
+
#include
#include
#include
@@ -29,6 +39,9 @@ static char *delim = "\\n";
static char *groupDelim = "\\n";
static int attrib = 0;
+static Redis *redis;
+static RedisCluster *cluster;
+
static void printVersion(const char *name) {
printf("%s %s\n", name, REDISX_VERSION_STRING);
}
@@ -56,10 +69,23 @@ static void printRESP(const RESP *resp) {
}
}
-static void process(Redis *redis, const char **cmdargs, int nargs) {
+static void process(const char **cmdargs, int nargs) {
int status = X_SUCCESS;
RESP *reply, *attr = NULL;
+ if(cluster) {
+ const char *key = cmdargs[1];
+
+ if(nargs > 3) if(strcasecmp("EVAL", cmdargs[0]) == 0 || strcasecmp("EVALSHA", cmdargs[0]) || strcasecmp("FCALL", cmdargs[0]))
+ key = cmdargs[3];
+
+ redis = redisxClusterGetShard(cluster, key);
+ if(!redis) {
+ fprintf(stderr, "ERROR! No suitable cluster node found for transaction.");
+ return;
+ }
+ }
+
reply = redisxArrayRequest(redis, cmdargs, NULL, nargs, &status);
if(!status && attrib) attr = redisxGetAttributes(redis);
@@ -103,18 +129,18 @@ static int interactive(Redis *redis) {
for(;;) {
char *line = readline(prompt);
- char **args;
+ const char **args;
int nargs;
if(!line) continue;
if(strcmp("quit", line) == 0 || strcmp("exit", line) == 0) break;
- poptParseArgvString(line, &nargs, (const char ***) &args);
+ poptParseArgvString(line, &nargs, &args);
if(args) {
if(nargs > 0) {
- process(redis, (const char **) args, nargs);
+ process(args, nargs);
add_history(line);
}
free(args);
@@ -164,15 +190,16 @@ static char *readScript(const char *eval) {
* @return The argument list to pass to Redis , of the form:
* `EVAL