Skip to content

Commit

Permalink
More fixes, some table tests, doc edits
Browse files Browse the repository at this point in the history
  • Loading branch information
attipaci committed Dec 10, 2024
1 parent 3540365 commit 8239ffd
Show file tree
Hide file tree
Showing 11 changed files with 308 additions and 60 deletions.
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,14 @@ prior to invoking `make`. The following build variables can be configured:

- `CPPFLAGS`: C preprocessor flags, such as externally defined compiler constants.

- `CFLAGS`: Flags to pass onto the C compiler (default: `-Os -Wall -std=c99`). Note, `-Iinclude` will be added
- `CFLAGS`: Flags to pass onto the C compiler (default: `-g -Os -Wall`). Note, `-Iinclude` will be added
automatically.

- `CSTANDARD`: Optionally, specify the C standard to compile for, e.g. `c99` to compile for the C99 standard. If
defined then `-std=$(CSTANDARD)` is added to `CFLAGS` automatically.

- `WEXTRA`: If set to 1, `-Wextra` is added to `CFLAGS` automatically.

- `LDFLAGS`: Extra linker flags (default is _not set_). Note, `-lm -lxchange` will be added automatically.

- `CHECKEXTRA`: Extra options to pass to `cppcheck` for the `make check` target
Expand Down Expand Up @@ -514,9 +519,15 @@ Note, that you can usually convert a RESP to an `XField`, and/or to JSON represe
RESP *resp = redisxGetHelloData(redis);

// Print the response from HELLO to the standard output in JSON format
printf("%s", redisxRESP2JSON("hello_response", resp));
char *json = redisxRESP2JSON("hello_response", resp);
if(json != NULL) {
printf("%s", json);
free(json);
}

...

// Destroy our copy of the RESP
// Clean up
redisxDestroyRESP(resp);
```c

Expand Down
11 changes: 9 additions & 2 deletions config.mk
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,17 @@ CC ?= gcc
CPPFLAGS += -I$(INC)

# Base compiler options (if not defined externally...)
CFLAGS ?= -g -Os -Wall -std=c99
CFLAGS ?= -g -Os -Wall

# Compile for specific C standard
ifdef CSTANDARD
CFLAGS += -std=$(CSTANDARD)
endif

# Extra warnings (not supported on all compilers)
#CFLAGS += -Wextra
ifeq ($(WEXTRA), 1)
CFLAGS += -Wextra
endif

# Extra linker flags (if any)
#LDFLAGS=
Expand Down
2 changes: 2 additions & 0 deletions include/redisx.h
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@ RESP *redisxExecBlockAsync(RedisClient *cl);
int redisxLoadScript(Redis *redis, const char *script, char **sha1);

int redisxGetTime(Redis *redis, struct timespec *t);
int redisxIsGlobPattern(const char *str);

RESP *redisxCopyOfRESP(const RESP *resp);
int redisxCheckRESP(const RESP *resp, enum resp_type expectedType, int expectedSize);
Expand All @@ -413,6 +414,7 @@ boolean redisxIsEqualRESP(const RESP *a, const RESP *b);
int redisxSplitText(RESP *resp, char **text);
XField *redisxRESP2XField(const char *name, const RESP *resp);
char *redisxRESP2JSON(const char *name, const RESP *resp);
int redisxPrintRESP(const char *name, const RESP *resp);

RedisMapEntry *redisxGetMapEntry(const RESP *map, const RESP *key);
RedisMapEntry *redisxGetKeywordEntry(const RESP *map, const char *key);
Expand Down
4 changes: 2 additions & 2 deletions src/redisx-client.c
Original file line number Diff line number Diff line change
Expand Up @@ -630,8 +630,8 @@ int redisxSendArrayRequestAsync(RedisClient *cl, char *args[], int lengths[], in
int l, L1;

if(!args[i]) l = 0; // Check for potential NULL parameters...
else if(!lengths) l = (int) strlen(args[i]);
else l = lengths[i] > 0 ? lengths[i] : (int) strlen(args[i]);
else if(lengths) l = lengths[i] > 0 ? lengths[i] : (int) strlen(args[i]);
else l = (int) strlen(args[i]);


L += sprintf(buf + L, "$%d\r\n", l);
Expand Down
20 changes: 2 additions & 18 deletions src/redisx-sub.c
Original file line number Diff line number Diff line change
Expand Up @@ -329,22 +329,6 @@ int redisxClearSubscribers(Redis *redis) {
return n;
}

/**
* Checks if a given string is a glob-style pattern.
*
* \param str The string to check.
*
* \return TRUE if it is a glob pattern (e.g. has '*', '?' or '['), otherwise FALSE.
*
*/
static int rIsGlobPattern(const char *str) {
for(; *str; str++) switch(*str) {
case '*':
case '?':
case '[': return TRUE;
}
return FALSE;
}

/**
* Subscribe to a specific Redis channel. The call will also start the subscription listener
Expand Down Expand Up @@ -387,7 +371,7 @@ int redisxSubscribe(Redis *redis, const char *pattern) {
prop_error(fn, status);

prop_error(fn, redisxLockConnected(redis->subscription));
status = redisxSendRequestAsync(redis->subscription, rIsGlobPattern(pattern) ? "PSUBSCRIBE" : "SUBSCRIBE", pattern, NULL, NULL);
status = redisxSendRequestAsync(redis->subscription, redisxIsGlobPattern(pattern) ? "PSUBSCRIBE" : "SUBSCRIBE", pattern, NULL, NULL);
redisxUnlockClient(redis->subscription);
prop_error(fn, status);

Expand Down Expand Up @@ -420,7 +404,7 @@ int redisxUnsubscribe(Redis *redis, const char *pattern) {
prop_error(fn, redisxLockConnected(redis->subscription));

if(pattern) {
status = redisxSendRequestAsync(redis->subscription, rIsGlobPattern(pattern) ? "PUNSUBSCRIBE" : "UNSUBSCRIBE", pattern, NULL, NULL);
status = redisxSendRequestAsync(redis->subscription, redisxIsGlobPattern(pattern) ? "PUNSUBSCRIBE" : "UNSUBSCRIBE", pattern, NULL, NULL);
}
else {
status = redisxSendRequestAsync(redis->subscription, "UNSUBSCRIBE", NULL, NULL, NULL);
Expand Down
85 changes: 51 additions & 34 deletions src/redisx-tab.c
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,15 @@ RedisEntry *redisxGetTable(Redis *redis, const char *table, int *n) {
// Cast RESP2 array respone to RESP3 map also...
if(reply && reply->type == RESP_ARRAY) {
reply->type = RESP3_MAP;
reply->n >>= 1;
reply->n /= 2;
}

*n = redisxCheckDestroyRESP(reply, RESP3_MAP, 0);
if(*n) {
return x_trace_null(fn, NULL);
}

*n = reply->n / 2;
*n = reply->n;

if(*n > 0) {
RedisMapEntry *dict = (RedisMapEntry *) reply->value;
Expand Down Expand Up @@ -201,8 +201,9 @@ int redisxSetValueAsync(RedisClient *cl, const char *table, const char *key, con
* \param[out] status (optional) pointer to the return error status, which is either X_SUCCESS on success or else
* the error code set by redisxArrayRequest(). It may be NULL if not required.
*
* \return A freshly allocated RESP array containing the Redis response, or NULL if no valid
* response could be obtained.
* \return A freshly allocated RESP containing the Redis response, or NULL if no valid
* response could be obtained. Values are returned as RESP_BULK_STRING (count = 1),
* or else type RESP_ERROR or RESP_NULL if Redis responded with an error or null, respectively.
*
* \sa redisxGetStringValue()
*/
Expand Down Expand Up @@ -326,10 +327,10 @@ int redisxMultiSetAsync(RedisClient *cl, const char *table, const RedisEntry *en
req[1] = (char *) table;

for(i=0; i<n; i++) {
int m = 2 + (n<<1);
req[m] = (char *) entries[n].key;
req[m+1] = (char *) entries[n].value;
L[m+1] = entries[n].length;
int m = 2 + (i<<1);
req[m] = (char *) entries[i].key;
req[m+1] = (char *) entries[i].value;
L[m+1] = entries[i].length;
}

if(!confirm) status = redisxSkipReplyAsync(cl);
Expand Down Expand Up @@ -367,14 +368,14 @@ int redisxMultiSet(Redis *redis, const char *table, const RedisEntry *entries, i
if(n < 1) return x_error(X_SIZE_INVALID, EINVAL, fn, "invalid size: %d", n);

prop_error(fn, redisxLockConnected(redis->interactive));

status = redisxMultiSetAsync(redis->interactive, table, entries, n, confirm);
if(status == X_SUCCESS && confirm) {
RESP *reply = redisxReadReplyAsync(redis->interactive);
status = redisxCheckRESP(reply, RESP_SIMPLE_STRING, 0);
if(!status) if(strcmp(reply->value, "OK")) status = REDIS_ERROR;
redisxDestroyRESP(reply);
}
redisxUnlockClient(redis->interactive);

prop_error(fn, status);

Expand Down Expand Up @@ -413,7 +414,7 @@ char **redisxGetKeys(Redis *redis, const char *table, int *n) {
return NULL;
}

reply = redisxRequest(redis, "HKEYS", table, NULL, NULL, n);
reply = redisxRequest(redis, table ? "HKEYS" : "KEYS", table ? table : "*", NULL, NULL, n);

if(*n) {
redisxDestroyRESP(reply);
Expand Down Expand Up @@ -567,8 +568,6 @@ char **redisxScanKeys(Redis *redis, const char *pattern, int *n, int *status) {
cmd[args++] = countArg;
}

xdprintf("Redis-X> Calling SCAN (MATCH %s)\n", pattern);

do {
int count;
RESP **components;
Expand Down Expand Up @@ -639,7 +638,6 @@ char **redisxScanKeys(Redis *redis, const char *pattern, int *n, int *status) {
if(!names) return NULL;

// Sort alphabetically.
xdprintf("Redis-X> Sorting %d scanned table entries.\n", *n);
qsort(names, *n, sizeof(char *), compare_strings);

// Remove duplicates
Expand Down Expand Up @@ -748,8 +746,6 @@ RedisEntry *redisxScanTable(Redis *redis, const char *table, const char *pattern
cmd[args++] = countArg;
}

xdprintf("Redis-X> Calling HSCAN %s (MATCH %s)\n", table, pattern);

do {
int count;
RESP **components;
Expand Down Expand Up @@ -832,7 +828,6 @@ RedisEntry *redisxScanTable(Redis *redis, const char *table, const char *pattern
if(!entries) return NULL;

// Sort alphabetically.
xdprintf("Redis-X> Sorting %d scanned table entries.\n", *n);
qsort(entries, *n, sizeof(RedisEntry), compare_entries);

// Remove duplicates
Expand All @@ -856,7 +851,16 @@ RedisEntry *redisxScanTable(Redis *redis, const char *table, const char *pattern
}

/**
* Destroy a RedisEntry array, such as returned e.g. by redisxScanTable()
* Destroy a RedisEntry array with dynamically allocate keys/values, such as returned e.g. by
* redisxScanTable().
*
* IMPORTANT:
*
* You should not use this function to destroy RedisEntry[] arrays, which contain static
* string references (keys or values). If the table contains only static references you can simply
* call free() on the table. Otherwise, you will have to first free only the dynamically sized
* string fields within before calling free() on the table itself.
*
*
* @param entries Pointer to the entries array (or single entry data). It may be NULL, in which
* case this call will return immediately.
Expand Down Expand Up @@ -908,46 +912,56 @@ void redisxDestroyKeys(char **keys, int count) {
int redisxDeleteEntries(Redis *redis, const char *pattern) {
static const char *fn = "redisxDeleteEntries";

char *root, *key;
char **keys;
int i, n = 0, status;
int i, n = 0, found = 0, status;

if(!pattern) return x_error(X_NULL, EINVAL, fn, "'pattern' is NULL");
if(!pattern[0]) return x_error(X_NULL, EINVAL, fn, "'pattern' is empty");

keys = redisxScanKeys(redis, pattern, &n, &status);
prop_error(fn, status);
// Separate the top-level component
root = xStringCopyOf(pattern);
xSplitID(root, &key);

if(!keys) return x_trace(fn, NULL, X_NULL);
if(redisxIsGlobPattern(root)) {
keys = redisxScanKeys(redis, root, &n, &status);
if(status || !keys) {
free(root);
prop_error(fn, status);
return x_trace(fn, NULL, X_NULL);
}
}
else {
keys = (char **) calloc(1, sizeof(char *));
x_check_alloc(keys);
keys[0] = xStringCopyOf(root);
n = 1;
}

for(i = 0; i < n ; i++) {
char *root, *key;
const char *table = keys[i];
RedisEntry *entries;
int nEntries;

// If the table itself matches, delete it wholesale...
if(fnmatch(pattern, table, 0) == 0) {
if(fnmatch(root, table, 0) == 0) {
RESP *reply = redisxRequest(redis, "DEL", table, NULL, NULL, &status);
if(redisxCheckDestroyRESP(reply, RESP_INT, 1) == X_SUCCESS) n++;
if(redisxCheckDestroyRESP(reply, RESP_INT, 1) == X_SUCCESS) found++;
continue;
}

// Look for table:key style patterns
root = xStringCopyOf(pattern);
xSplitID(root, &key);

// Otherwise check the table entries...
entries = redisxScanTable(redis, table, root, &nEntries, &status);
entries = redisxScanTable(redis, table, key, &nEntries, &status);
if(status == X_SUCCESS) {
int k;
for(k = 0; k < nEntries; k++) {
const RedisEntry *e = &entries[k];
char *id = xGetAggregateID(table, e->key);

if(id) {
if(fnmatch(pattern, id, 0) == 0) {
if(fnmatch(key, id, 0) == 0) {
RESP *reply = redisxRequest(redis, "HDEL", table, e->key, NULL, &status);
if(redisxCheckDestroyRESP(reply, RESP_INT, 1) == X_SUCCESS) n++;
if(redisxCheckDestroyRESP(reply, RESP_INT, 1) == X_SUCCESS) found++;
}
free(id);
}
Expand All @@ -957,11 +971,14 @@ int redisxDeleteEntries(Redis *redis, const char *pattern) {
}
}

free(root);

if(entries) free(entries);
}
return n;

redisxDestroyKeys(keys, n);
free(root);


return found;
}

#endif
17 changes: 17 additions & 0 deletions src/redisx.c
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,23 @@ RESP *redisxGetHelloData(Redis *redis) {
return data;
}

/**
* Checks if a given string is a glob-style pattern.
*
* \param str The string to check.
*
* \return TRUE if it is a glob pattern (e.g. has '*', '?' or '['), otherwise FALSE.
*
*/
int redisxIsGlobPattern(const char *str) {
for(; *str; str++) switch(*str) {
case '*':
case '?':
case '[': return TRUE;
}
return FALSE;
}

/**
* Returns a string description for one of the RM error codes.
*
Expand Down
18 changes: 18 additions & 0 deletions src/resp.c
Original file line number Diff line number Diff line change
Expand Up @@ -744,4 +744,22 @@ char *redisxRESP2JSON(const char *name, const RESP *resp) {
return xjsonFieldToString(redisxRESP2XField(name, resp));
}

/**
* Prints a RESP in JSON format to the standard output with the specified name
*
* @param name The name/ID to assign to the RESP
* @param resp The RESP data to print
* @return 0
*/
int redisxPrintRESP(const char *name, const RESP *resp) {
char *json = redisxRESP2JSON("hello_response", resp);

if(json) {
printf("%s", json);
free(json);
}
else printf("\"%s\": null\n", name);

return X_SUCCESS;
}

3 changes: 2 additions & 1 deletion test/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ include ../config.mk
all: tests run

.PHONY: tests
tests: $(BIN)/test-hello
tests: $(BIN)/test-hello $(BIN)/test-tab

.PHONY: run
run: tests
$(BIN)/test-hello
$(BIN)/test-tab

$(BIN)/test-%: $(OBJ)/test-%.o $(LIB)/libredisx.a
make $(BIN)
Expand Down
Loading

0 comments on commit 8239ffd

Please sign in to comment.