Browse Source

Add KEYS, EXPIRE & EXPIREAT commands

tagged: v0.2.0

Also includes:
- Updated README
- Bug fixes in resp.c for missing null terminator handling or leaked
internal strings
- Fix old-code-caused bug in object.c#RedisObject_parseArray()
- Exposed RedisArray_dealloc(), for callers of Redis_KEYS()
- Updated test.c for new commands
tags/v0.2.0
Ryan Joseph 11 months ago
parent
commit
c2ef543885
8 changed files with 148 additions and 64 deletions
  1. +17
    -9
      README.md
  2. +75
    -38
      src/commands.c
  3. +10
    -2
      src/commands.h
  4. +1
    -2
      src/object.c
  5. +10
    -5
      src/resp.c
  6. +2
    -0
      src/resp.h
  7. +5
    -2
      src/yarl.h
  8. +28
    -6
      test/test.c

+ 17
- 9
README.md View File

@@ -3,23 +3,31 @@ Yet Another Redis Library

A couple [dev](https://www.nordicsemi.com/?sc_itemid=%7BF2C2DBF4-4D5C-4EAD-9F3D-CFD0276B300B%7D)
[kits](http://cloudconnectkits.org/product/azure-sphere-starter-kit) arrived
that wanted for a POSIX C Redis library, so this
is a port of [arduino-redis](http://arduino-redis.com) into straight-C.
that wanted for a POSIX C Redis library, so this is a port of [arduino-redis](http://arduino-redis.com) into straight-C.
Some day, that library will consume this one & morph into just a nice C++ facade.

Additionally, I felt a simple, "bring your own file descriptor" interface
would do nicely on the simple MCU platforms to be targetted.

To that end, `RedisConnection_t` is `typedef`ed simply to `int`, a.k.a. a
Given the simple MCU platforms being targetted, a "bring your own file descriptor" interface
seemed like it would do nicely. To that end, `RedisConnection_t` is `typedef`ed simply to `int`, a.k.a. a
file descriptor. It could be a socket FD, a pipe FD, even an actual file descriptor
if you're so inclined. Anything that can be [`read(2)`](http://man7.org/linux/man-pages/man2/read.2.html)
from and [`write(2)`](http://man7.org/linux/man-pages/man2/write.2.html)-en to. BYOFD!

## Building

The test app:
### The test app
Should build & run on any POSIX platform.

```
clang -o yarl_test -Wall -Werror -I./src -O0 -g ./src/*.c ./test/test.c
clang -o yarl_test -Wall -Werror -I./src ./src/*.c ./test/test.c
```

(add `-DDEBUG=1` to generate debugging logging)
* add `-DDEBUG=1` to generate debugging logging
* add `-O0 -g2` to disable optimizations and generate symbols, to enable debugging via `lldb`/`gdb`

### The Azure Sphere library

Requires the [Azure Sphere SDK](https://docs.microsoft.com/en-us/azure-sphere/app-development/development-environment) (so also Windows & VS17 or VS19).

The VS project in the `azuresphere` directory is preconfigured to build a static library named `libyarl.a` in the standard build output location.

See [this project](https://github.com/rpj/spheremon) for an example of consuming yarl as a build dependency.

+ 75
- 38
src/commands.c View File

@@ -1,6 +1,8 @@
#include "commands.h"
#include "connection.h"
#include "object.h"
#include "resp.h"
#include "log.h"
#include <stdint.h>
#include <string.h>
@@ -10,21 +12,22 @@
// optional arguments must all be of type void* (well, really just any pointer type will do)
// ownershipBitMask allows marking which of the optional arguments is owned: the index for which the argument is passed is
// checked against that bit in ownershipBitMask, and if set is marked as "owned" by us
RedisObject_t _RedisCommand_issue(RedisConnection_t conn, const char* cmd, uint32_t argCount, uint32_t ownershipBitMask, ...)
RedisObject_t _RedisCommand_issue(RedisConnection_t, const char *, uint32_t, uint32_t, ...);
RedisObject_t _RedisCommand_issue(RedisConnection_t conn, const char *cmd, uint32_t argCount, uint32_t ownershipBitMask, ...)
{
assert(cmd);
// 'argCount + 1' to account for the cmd object that must always be included
RedisArray_t* cmdArr = RedisArray_init(argCount + 1);
RedisArray_t *cmdArr = RedisArray_init(argCount + 1);
RedisObject_t cmdObj = {
.type = RedisObjectType_Array,
.obj = (void *)cmdArr,
.objIsOwned = true };
.objIsOwned = true};
RedisObject_t cmdNameObj = {
.type = RedisObjectType_BulkString,
.obj = (void *)cmd,
.objIsOwned = false };
.objIsOwned = false};
cmdArr->objects[0] = cmdNameObj;
@@ -36,9 +39,8 @@ RedisObject_t _RedisCommand_issue(RedisConnection_t conn, const char* cmd, uint3
{
RedisObject_t argObj = {
.type = RedisObjectType_BulkString,
.obj = (void*)va_arg(args, void*),
.objIsOwned = (bool)(ownershipBitMask & (uint32_t)(1 << i))
};
.obj = (void *)va_arg(args, void *),
.objIsOwned = (bool)(ownershipBitMask & (uint32_t)(1 << i))};
cmdArr->objects[i + 1] = argObj;
}
@@ -49,64 +51,99 @@ RedisObject_t _RedisCommand_issue(RedisConnection_t conn, const char* cmd, uint3
RedisObject_dealloc(cmdObj);
return cmdResult;
}
bool _RedisCommandReturn__typeCheck(RedisObject_t, RedisObjectType_t);
bool _RedisCommandReturn__typeCheck(RedisObject_t cmdRet, RedisObjectType_t expectType)
{
return cmdRet.type != RedisObjectType_InternalError && cmdRet.type == expectType && cmdRet.obj;
}
#define REDIS_CMD__GENERIC__PREDEALLOC(conn, cmd, count, bm, endCond, failReturn, preDealloc, deallocBeforeReturn, ...) \
RedisObject_t cmdRet = _RedisCommand_issue(conn, cmd, count, bm, ##__VA_ARGS__); \
if (cmdRet.type == RedisObjectType_InternalError || !cmdRet.obj) return failReturn; \
preDealloc; \
if (deallocBeforeReturn) RedisObject_dealloc(cmdRet); \
return endCond;
#define REDIS_CMD__GENERIC(conn, cmd, count, bm, endCond, failReturn, deallocBeforeReturn, ...) \
RedisObject_t cmdRet = _RedisCommand_issue(conn, cmd, count, bm, ##__VA_ARGS__); \
if (cmdRet.type == RedisObjectType_InternalError || !cmdRet.obj) return failReturn; \
if (deallocBeforeReturn) RedisObject_dealloc(cmdRet); \
return endCond;
#define REDIS_CMD__GENERIC(expectType, conn, cmd, count, bm, successReturn, failReturn, dealloc, ...) \
RedisObject_t cmdRet = _RedisCommand_issue(conn, cmd, count, bm, ##__VA_ARGS__); \
\
if (dealloc) \
RedisObject_dealloc(cmdRet); \
\
return _RedisCommandReturn__typeCheck(cmdRet, expectType) ? successReturn : failReturn;
#define _RedisCommandReturn_(name, retType, exType, failReturn, dealloc, extractSt) \
retType _RedisCommandReturn_##name(RedisObject_t); \
retType _RedisCommandReturn_##name(RedisObject_t cmdRet) \
{ \
if (!_RedisCommandReturn__typeCheck(cmdRet, exType)) \
{ \
RedisLog("_RedisCommandReturn__typeCheck failed: %d\n", exType); \
return failReturn; \
} \
\
retType retVal = extractSt; \
\
if (dealloc) \
RedisObject_dealloc(cmdRet); \
\
return retVal; \
}
#define REDIS_CMD__EXPECT_OK(conn, cmd, count, bm, ...) \
REDIS_CMD__GENERIC__PREDEALLOC(conn, cmd, count, bm, boolVal, false, \
bool boolVal = cmdRet.type == RedisObjectType_SimpleString && cmdRet.obj && \
strncmp((const char *)cmdRet.obj, "OK", strlen("OK")) == 0, true, ##__VA_ARGS__)
_RedisCommandReturn_(extractBool, bool, RedisObjectType_Integer, false, true, (bool)*(int *)cmdRet.obj);
#define REDIS_CMD__EXPECT_BOOL(conn, cmd, count, bm, ...) \
REDIS_CMD__GENERIC__PREDEALLOC(conn, cmd, count, bm, boolVal, false, \
bool boolVal = cmdRet.type == RedisObjectType_Integer && (bool)*(int*)cmdRet.obj, true, ##__VA_ARGS__)
_RedisCommandReturn_(extractInt, int, RedisObjectType_Integer, -1, true, *(int *)cmdRet.obj);
#define REDIS_CMD__EXPECT_INT(conn, cmd, count, bm, ...) \
REDIS_CMD__GENERIC__PREDEALLOC(conn, cmd, count, bm, intVal, -1, \
int intVal = *(int*)cmdRet.obj, true, ##__VA_ARGS__)
_RedisCommandReturn_(isOKString, bool, RedisObjectType_SimpleString, false, true,
strncmp((const char *)cmdRet.obj, "OK", strlen("OK")) == 0);
bool Redis_AUTH(RedisConnection_t conn, const char *password)
{
REDIS_CMD__EXPECT_OK(conn, "AUTH", 1, 0, password);
return _RedisCommandReturn_isOKString(_RedisCommand_issue(conn, "AUTH", 1, 0, password));
}
char *Redis_GET(RedisConnection_t conn, const char *key)
{
REDIS_CMD__GENERIC(conn, "GET", 1, 0, (char*)cmdRet.obj, NULL, false, key);
REDIS_CMD__GENERIC(RedisObjectType_BulkString, conn, "GET", 1, 0, (char *)cmdRet.obj, NULL, false, key);
}
bool Redis_SET(RedisConnection_t conn, const char *key, const char *value)
{
REDIS_CMD__EXPECT_OK(conn, "SET", 2, 0, key, value);
return _RedisCommandReturn_isOKString(_RedisCommand_issue(conn, "SET", 2, 0, key, value));
}
bool Redis_DEL(RedisConnection_t conn, const char *key)
{
REDIS_CMD__EXPECT_BOOL(conn, "DEL", 1, 0, key);
return _RedisCommandReturn_extractBool(_RedisCommand_issue(conn, "DEL", 1, 0, key));
}
bool Redis_EXISTS(RedisConnection_t conn, const char *key)
{
REDIS_CMD__EXPECT_BOOL(conn, "EXISTS", 1, 0, key);
return _RedisCommandReturn_extractBool(_RedisCommand_issue(conn, "EXISTS", 1, 0, key));
}
int Redis_APPEND(RedisConnection_t conn, const char *key, const char *value)
{
REDIS_CMD__EXPECT_INT(conn, "APPEND", 2, 0, key, value);
return _RedisCommandReturn_extractInt(_RedisCommand_issue(conn, "APPEND", 2, 0, key, value));
}
int Redis_PUBLISH(RedisConnection_t conn, const char* channel, const char* message)
int Redis_PUBLISH(RedisConnection_t conn, const char *channel, const char *message)
{
REDIS_CMD__EXPECT_INT(conn, "PUBLISH", 2, 0, channel, message);
}
return _RedisCommandReturn_extractInt(_RedisCommand_issue(conn, "PUBLISH", 2, 0, channel, message));
}
RedisArray_t *Redis_KEYS(RedisConnection_t conn, const char *pattern)
{
REDIS_CMD__GENERIC(RedisObjectType_Array, conn, "KEYS", 1, 0, (RedisArray_t *)cmdRet.obj, NULL, false, pattern);
}
bool _Redis_EXPIRE_generic(RedisConnection_t conn, const char *cmd, const char *key, int num)
{
char *numStr = _RedisObject_RESP__intAsStringWithLength(num, NULL);
bool retVal = _RedisCommandReturn_extractBool(_RedisCommand_issue(conn, cmd, 2, 0, key, numStr));
free(numStr);
return retVal;
}
bool Redis_EXPIRE(RedisConnection_t conn, const char *key, int seconds)
{
return _Redis_EXPIRE_generic(conn, "EXPIRE", key, seconds);
}
bool Redis_EXPIREAT(RedisConnection_t conn, const char *key, int timestamp)
{
return _Redis_EXPIRE_generic(conn, "EXPIREAT", key, timestamp);
}

+ 10
- 2
src/commands.h View File

@@ -4,6 +4,8 @@
#include "types.h"
#include "constants.h"
#include <stdbool.h>
// any non-scalar return values are OWNED BY THE CALLER and must be free()ed
bool Redis_AUTH(RedisConnection_t conn, const char *password);
@@ -18,6 +20,12 @@ bool Redis_EXISTS(RedisConnection_t conn, const char *key);
int Redis_APPEND(RedisConnection_t conn, const char *key, const char *value);
int Redis_PUBLISH(RedisConnection_t conn, const char* channel, const char* message);
int Redis_PUBLISH(RedisConnection_t conn, const char *channel, const char *message);
RedisArray_t *Redis_KEYS(RedisConnection_t conn, const char *pattern);
bool Redis_EXPIRE(RedisConnection_t conn, const char *key, int seconds);
bool Redis_EXPIREAT(RedisConnection_t conn, const char *key, int timestamp);
#endif // __YARL_COMMANDS__H__
#endif // __YARL_COMMANDS__H__

+ 1
- 2
src/object.c View File

@@ -91,8 +91,7 @@ RedisObject_t RedisObject_parseArray(RedisConnection_t conn)
RedisObject_dealloc(lenObj);
assert(len >= 0);
RedisArray_t *retArr = (RedisArray_t *)malloc(sizeof(RedisArray_t));
retArr->count = (size_t)len;
RedisArray_t *retArr = RedisArray_init(len);
for (int i = 0; i < len; i++)
retArr->objects[i] = RedisConnection_getNextObject(conn);


+ 10
- 5
src/resp.c View File

@@ -11,7 +11,9 @@
char *_RedisObject_RESP__allocWithLen(RedisObject_t obj, size_t emitLen)
{
char *emitStr = (char *)malloc(emitLen + 1);
// the null terminator (just in case the caller forgot, which does tend to happen)
emitLen += 1;
char *emitStr = (char *)malloc(emitLen);
bzero(emitStr, emitLen);
emitStr[0] = (char)obj.type;
return emitStr;
@@ -29,7 +31,8 @@ char *_RedisObject_RESP__intAsStringWithLength(size_t num, size_t *outLength)
if (outLength)
*outLength = _sslslt;
char *ret = (char *)malloc(_sslslt);
// account for trailing null
char *ret = (char *)malloc(++_sslslt);
memcpy(ret, ssLenStr, _sslslt);
return ret;
}
@@ -68,6 +71,7 @@ char *RedisObject_RESP_bulkString(RedisObject_t obj)
memcpy(copyPtr, ssLenStr, ssLenStrLen);
copyPtr += ssLenStrLen;
free(ssLenStr);
memcpy(copyPtr, CRLF, crlfLen);
copyPtr += crlfLen;
memcpy(copyPtr, ss, ssLen);
@@ -98,7 +102,7 @@ char *RedisObject_RESP_array(RedisObject_t obj)
char *tRESP = RedisRESP_generate(rArr->objects[i]);
_RedisObject_RESP_array__coll_t tCol = {
.RESP = tRESP,
.length = strlen(tRESP) };
.length = strlen(tRESP)};
constRESPs[i] = tCol;
constRESPsTotalLen += tCol.length;
}
@@ -112,6 +116,7 @@ char *RedisObject_RESP_array(RedisObject_t obj)
memcpy(copyPtr, arrLenStr, arrLenStrLen);
copyPtr += arrLenStrLen;
free(arrLenStr);
memcpy(copyPtr, CRLF, strlen(CRLF));
copyPtr += strlen(CRLF);
@@ -137,7 +142,7 @@ static RedisObjectRESPTypeMap_t RedisObject_RESP_map[] = {
{RedisObjectType_Integer, RedisObject_RESP_simpleString},
{RedisObjectType_Array, RedisObject_RESP_array},
{RedisObjectType_Error, RedisObject_RESP_simpleString},
{RedisObjectType_InternalError, NULL} };
{RedisObjectType_InternalError, NULL}};
char *RedisRESP_generate(RedisObject_t obj)
{
@@ -151,4 +156,4 @@ char *RedisRESP_generate(RedisObject_t obj)
}
return NULL;
}
}

+ 2
- 0
src/resp.h View File

@@ -5,4 +5,6 @@
char *RedisRESP_generate(RedisObject_t obj);
char *_RedisObject_RESP__intAsStringWithLength(size_t num, size_t *outLength);
#endif // __YARL_RESP__H__

+ 5
- 2
src/yarl.h View File

@@ -5,12 +5,15 @@
extern "C" {
#endif
#define YARL_VERSION "0.1.1"
#define YARL_VERSION "0.2.0"
#define YARL_VERSION_NUMERIC 020
#include "commands.h"
extern void RedisArray_dealloc(RedisArray_t *);
#ifdef __cplusplus
}
#endif
#endif // __YARL_H__
#endif // __YARL_H__

+ 28
- 6
test/test.c View File

@@ -75,9 +75,9 @@ RedisConnection_t RedisConnect(const char *host, const char *port)
return (RedisConnection_t)sockfd;
}
#define TEST_KEY_NAME "YarlSaysHi"
#define TEST_KEY_NAME "YarlSaysHi"
int main(int argc, char** argv)
int main(int argc, char **argv)
{
if (argc < 3)
{
@@ -85,9 +85,9 @@ int main(int argc, char** argv)
exit(-1);
}
const char* host = argv[1];
const char* port = argv[2];
const char* pass = argc > 3 ? argv[3] : NULL;
const char *host = argv[1];
const char *port = argv[2];
const char *pass = argc > 3 ? argv[3] : NULL;
RedisConnection_t rConn = RedisConnect(host, port);
@@ -118,7 +118,7 @@ int main(int argc, char** argv)
exit(-2);
}
char* getVal = Redis_GET(rConn, TEST_KEY_NAME);
char *getVal = Redis_GET(rConn, TEST_KEY_NAME);
printf("Hello, World? %s\n", getVal);
free(getVal);
@@ -134,4 +134,26 @@ int main(int argc, char** argv)
}
printf("Publish result: %d\n", Redis_PUBLISH(rConn, TEST_KEY_NAME, "Pub/sub is the bee's knees!"));
RedisArray_t *allKeys = Redis_KEYS(rConn, "*");
if (allKeys)
{
printf("Found %lu keys:\n", allKeys->count);
for (int i = 0; i < allKeys->count; i++)
printf("\t%s\n", (char *)allKeys->objects[i].obj);
RedisArray_dealloc(allKeys);
}
else
{
fprintf(stderr, "KEYS failed\n");
exit(-1);
}
if (!Redis_SET(rConn, TEST_KEY_NAME "ButWillExpireInSixty", "60...") ||
!Redis_EXPIRE(rConn, TEST_KEY_NAME "ButWillExpireInSixty", 60))
{
fprintf(stderr, "EXPIRE failed\n");
exit(-2);
}
}

Loading…
Cancel
Save