diff --git a/config.mk b/config.mk index 7052e71..d27e4d1 100644 --- a/config.mk +++ b/config.mk @@ -27,7 +27,7 @@ CC ?= gcc CPPFLAGS += -I$(INC) # Base compiler options (if not defined externally...) -CFLAGS ?= -Os -Wall -std=c99 +CFLAGS ?= -g -Os -Wall -std=c99 # Extra warnings (not supported on all compilers) #CFLAGS += -Wextra diff --git a/include/redisx.h b/include/redisx.h index e956713..8464b9e 100644 --- a/include/redisx.h +++ b/include/redisx.h @@ -159,10 +159,11 @@ enum redisx_protocol { */ typedef struct RESP { enum resp_type type; ///< RESP type RESP_ARRAY, RESP_INT ... - int n; ///< Either the integer value of a RESP_INT response, or the + int n; ///< Either the integer value of a RESP_INT or a RESP3_BOOLEAN response, or the ///< dimension of the value field. void *value; ///< Pointer to text (char *) content or to an array of components - ///< (RESP**)... + ///< (RESP**) or (RedisMapEntry *), or else a pointer to a `double`, depending + ///< on `type`. } RESP; /** @@ -186,7 +187,7 @@ typedef struct { typedef struct RedisEntry { char *key; ///< The Redis key or field name char *value; ///< The string value stored for that field. - int length; ///< Bytes in value. + int length; ///< Bytes in value. } RedisEntry; diff --git a/src/redisx-client.c b/src/redisx-client.c index b1b8f3e..4776d7e 100644 --- a/src/redisx-client.c +++ b/src/redisx-client.c @@ -601,6 +601,16 @@ int redisxSendArrayRequestAsync(RedisClient *cl, char *args[], int lengths[], in // Send the number of string elements in the command... L = sprintf(buf, "*%d\r\n", n); + + xvprintf("Redis-X> request[%d]", n); + for(i = 0; i < n; i++) { + if(args[i]) xvprintf(" %s", args[i]); + if(i == 4) { + xvprintf("..."); + } + } + xvprintf("\n"); + for(i = 0; i < n; i++) { int l, L1; @@ -608,6 +618,7 @@ int redisxSendArrayRequestAsync(RedisClient *cl, char *args[], int lengths[], in else if(!lengths) l = (int) strlen(args[i]); else l = lengths[i] > 0 ? lengths[i] : (int) strlen(args[i]); + L += sprintf(buf + L, "$%d\r\n", l); // length of next RESP the bulk string component including \r\n\0 termination. @@ -876,7 +887,6 @@ RESP *redisxReadReplyAsync(RedisClient *cl) { else break; } - // Now get the body of the response... if(!status) switch(resp->type) { @@ -932,7 +942,7 @@ RESP *redisxReadReplyAsync(RedisClient *cl) { } // Consistency check. Discard response if incomplete (because of read errors...) - if(component) for(i = 0; i < resp->n; i++) if(component[i] == NULL || component[i]->type != RESP3_NULL) { + if(component) for(i = 0; i < resp->n; i++) if(component[i] == NULL || component[i]->type == RESP3_NULL) { fprintf(stderr, "WARNING! Redis-X : incomplete array received (index %d of %d).\n", (i+1), resp->n); if(!status) status = REDIS_INCOMPLETE_TRANSFER; break; @@ -945,18 +955,21 @@ RESP *redisxReadReplyAsync(RedisClient *cl) { case RESP3_MAP: case RESP3_ATTRIBUTE: { - RedisMapEntry *component; + RedisMapEntry *dict; int i; + if(resp->n <= 0) break; - component = (RedisMapEntry *) calloc(resp->n, sizeof(RedisMapEntry)); - x_check_alloc(component); + dict = (RedisMapEntry *) calloc(resp->n, sizeof(RedisMapEntry)); + x_check_alloc(dict); for(i=0; in; i++) { - RedisMapEntry *e = &component[i]; + RedisMapEntry *e = &dict[i]; e->key = redisxReadReplyAsync(cl); e->value = redisxReadReplyAsync(cl); } + resp->value = dict; + break; } diff --git a/src/redisx-net.c b/src/redisx-net.c index ec2405b..ad912c2 100644 --- a/src/redisx-net.c +++ b/src/redisx-net.c @@ -573,7 +573,6 @@ int rConnectClient(Redis *redis, enum redisx_channel channel) { if(p->hello) { char proto[20]; char *args[6]; - RESP *reply; int k = 0; args[k++] = "HELLO"; @@ -584,21 +583,30 @@ int rConnectClient(Redis *redis, enum redisx_channel channel) { if(p->password) { args[k++] = "AUTH"; + args[k++] = p->username ? p->username : "default"; args[k++] = p->password; } args[k++] = "SETNAME"; args[k++] = id; - reply = redisxArrayRequest(redis, args, NULL, k, &status); - if(redisxCheckRESP(reply, RESP3_MAP, 0)) { - // OK, it looks like HELLO worked... - RedisMapEntry *e = redisxGetKeywordEntry(reply, "proto"); - if(e && e->value->type == RESP_INT) p->protocol = e->value->n; + p->hello = FALSE; + + status = redisxSendArrayRequestAsync(cl, args, NULL, k); + if(status == X_SUCCESS) { + RESP *reply = redisxReadReplyAsync(cl); + status = redisxCheckRESP(reply, RESP3_MAP, 0); + if(status == X_SUCCESS) { + RedisMapEntry *e = redisxGetKeywordEntry(reply, "proto"); + if(e && e->value->type == RESP_INT) { + p->protocol = e->value->n; + xvprintf("Confirmed protocol %d\n", p->protocol); + } + p->hello = TRUE; + } + else xvprintf("! Redis-X: HELLO failed: %s\n", redisxErrorDescription(status)); + redisxDestroyRESP(reply); } - else p->hello = FALSE; - - redisxDestroyRESP(reply); } if(!p->hello) { diff --git a/src/redisx-tab.c b/src/redisx-tab.c index 5578844..5fc5b72 100644 --- a/src/redisx-tab.c +++ b/src/redisx-tab.c @@ -139,9 +139,7 @@ int redisxSetValue(Redis *redis, const char *table, const char *key, const char if(redis == NULL) return x_error(X_NULL, EINVAL, fn, "redis is NULL"); prop_error(fn, redisxLockConnected(redis->interactive)); - status = redisxSetValueAsync(redis->interactive, table, key, value, confirm); - redisxUnlockClient(redis->interactive); prop_error(fn, status); diff --git a/src/redisx.c b/src/redisx.c index 673fffa..05f9bf2 100644 --- a/src/redisx.c +++ b/src/redisx.c @@ -601,8 +601,6 @@ RESP *redisxArrayRequest(Redis *redis, char *args[], int lengths[], int n, int * } else *status = X_SUCCESS; - xvprintf("Redis-X> request %s... [%d].\n", args[0], n); - cl = redis->interactive; *status = redisxLockConnected(cl); if(*status) return x_trace_null(fn, NULL); diff --git a/src/resp.c b/src/resp.c index 5299da6..51a88f5 100644 --- a/src/resp.c +++ b/src/resp.c @@ -34,7 +34,7 @@ void redisxDestroyRESP(RESP *resp) { if(resp == NULL) return; - switch(resp->type) { + if(resp->value) switch(resp->type) { case RESP_ARRAY: case RESP3_SET: case RESP3_PUSH: { @@ -151,6 +151,9 @@ int redisxCheckRESP(const RESP *resp, enum resp_type expectedType, int expectedS static const char *fn = "redisxCheckRESP"; if(resp == NULL) return x_error(X_NULL, EINVAL, fn, "RESP is NULL"); + if(resp->type == RESP3_BOOLEAN) { + if(resp->n != (expectedSize ? 1 : 0)) return x_error(X_FAILURE, EBADMSG, fn, "unexpected boolean value: expected %d, got %d", (expectedSize ? 1 : 0), resp->n); + } if(resp->type != RESP_INT && resp->type != RESP3_NULL) { if(resp->n < 0) return x_error(X_FAILURE, EBADMSG, fn, "RESP error code: %d", resp->n); if(resp->value == NULL) if(resp->n) return x_error(REDIS_NULL, ENOMSG, fn, "RESP with NULL value, n=%d", resp->n); diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 0000000..ad7635f --- /dev/null +++ b/test/Makefile @@ -0,0 +1,35 @@ +# Use the parent directories for libraries and headers. +LIB = ../lib +INC = ../include +BUILD_MODE = debug + +ifdef XCHANGE + XCHANGE := ../$(XCHANGE) +endif + +# Load the common Makefile definitions... +include ../config.mk + +.PHONY: all +all: tests run + +.PHONY: tests +tests: $(BIN)/test-hello + +.PHONY: run +run: tests + $(BIN)/test-hello + +$(BIN)/test-%: $(OBJ)/test-%.o $(LIB)/libredisx.a + make $(BIN) + $(CC) -o $@ $^ $(LDFLAGS) -lredisx + +.PHONY: clean-test +clean-test: + rm -rf bin + +clean: clean-test + +# Finally, the standard generic rules and targets... +include ../build.mk + diff --git a/test/src/test-hello.c b/test/src/test-hello.c new file mode 100644 index 0000000..1d5f47a --- /dev/null +++ b/test/src/test-hello.c @@ -0,0 +1,53 @@ +/** + * @file + * + * @date Created on Dec 8, 2024 + * @author Attila Kovacs + */ + +#include +#include +#include + +#include "redisx.h" +#include "xchange.h" + +int main() { + + Redis *redis = redisxInit("localhost"); + RedisEntry *e; + int n = -1; + + xSetDebug(TRUE); + //redisxSetVerbose(TRUE); + //redisxDebugTraffic(TRUE); + + redisxSetProtocol(redis, REDISX_RESP3); + + if(redisxConnect(redis, FALSE) < 0) { + perror("ERROR! connect"); + return 1; + } + + if(redisxGetProtocol(redis) != REDISX_RESP3) { + fprintf(stderr, "ERROR! verify RESP3 protocol\n"); + return 1; + } + + if(redisxSetValue(redis, "_test_", "_value_", "1", TRUE) < 0) { + perror("ERROR! set value"); + return 1; + } + + e = redisxGetTable(redis, "_test_", &n); + if(n <= 0) return 1; + + redisxDestroyEntries(e, n); + redisxDisconnect(redis); + redisxDestroy(redis); + + fprintf(stderr, "OK\n"); + + return 0; + +}