aboutsummaryrefslogtreecommitdiff
path: root/jim-redis.c
diff options
context:
space:
mode:
authorSteve Bennett <steveb@workware.net.au>2020-11-14 14:43:41 +1000
committerSteve Bennett <steveb@workware.net.au>2020-11-15 20:05:52 +1000
commitbe4c0e8732445172d52dfb38b56c8aa997762309 (patch)
tree3af6160b81a898427a0a96b45829176361ec3502 /jim-redis.c
parentc436661c19ac193007af0d2d878f554bad08f905 (diff)
downloadjimtcl-be4c0e8732445172d52dfb38b56c8aa997762309.zip
jimtcl-be4c0e8732445172d52dfb38b56c8aa997762309.tar.gz
jimtcl-be4c0e8732445172d52dfb38b56c8aa997762309.tar.bz2
redis: Add simple redis client extension
Using the hiredis client library. Synchronous API only. Signed-off-by: Steve Bennett <steveb@workware.net.au>
Diffstat (limited to 'jim-redis.c')
-rw-r--r--jim-redis.c168
1 files changed, 168 insertions, 0 deletions
diff --git a/jim-redis.c b/jim-redis.c
new file mode 100644
index 0000000..d1e9457
--- /dev/null
+++ b/jim-redis.c
@@ -0,0 +1,168 @@
+/*
+ * Simple redis interface
+ *
+ * (c) 2020 Steve Bennett <steveb@workware.net.au>
+ *
+ * See LICENSE for license details.
+ */
+#include <jim.h>
+#include <jim-eventloop.h>
+#include <unistd.h>
+#include <hiredis.h>
+
+/**
+ * Recursively decode a redis reply as Tcl data structure.
+ */
+static Jim_Obj *jim_redis_get_result(Jim_Interp *interp, redisReply *reply)
+{
+ int i;
+ switch (reply->type) {
+ case REDIS_REPLY_INTEGER:
+ return Jim_NewIntObj(interp, reply->integer);
+ case REDIS_REPLY_STATUS:
+ case REDIS_REPLY_ERROR:
+ case REDIS_REPLY_STRING:
+ return Jim_NewStringObj(interp, reply->str, reply->len);
+ break;
+ case REDIS_REPLY_ARRAY:
+ {
+ Jim_Obj *obj = Jim_NewListObj(interp, NULL, 0);
+ for (i = 0; i < reply->elements; i++) {
+ Jim_ListAppendElement(interp, obj, jim_redis_get_result(interp, reply->element[i]));
+ }
+ return obj;
+ }
+ case REDIS_REPLY_NIL:
+ return Jim_NewStringObj(interp, NULL, 0);
+ default:
+ return Jim_NewStringObj(interp, "badtype", -1);
+ }
+}
+
+/**
+ * $r readable ?script?
+ * - set or clear a readable script
+ * $r close
+ * - close (delete) the handle
+ * $r read
+ * - synchronously read a SUBSCRIBE response (typically from within readable)
+ * $r <redis-command> ...
+ * - invoke the redis command and return the decoded result
+ */
+static int jim_redis_subcmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ int i;
+ redisContext *c = Jim_CmdPrivData(interp);
+ const char **args;
+ size_t *arglens;
+ int ret = JIM_OK;
+
+ redisReply *reply;
+
+ if (argc < 2) {
+ Jim_WrongNumArgs(interp, 1, argv, "cmd ?args ...?");
+ return JIM_ERR;
+ }
+
+ if (Jim_CompareStringImmediate(interp, argv[1], "readable")) {
+ /* Remove any existing handler */
+ Jim_DeleteFileHandler(interp, c->fd, JIM_EVENT_READABLE);
+ if (argc > 2) {
+ Jim_CreateScriptFileHandler(interp, c->fd, JIM_EVENT_READABLE, argv[2]);
+ }
+ return JIM_OK;
+ }
+ if (Jim_CompareStringImmediate(interp, argv[1], "close")) {
+ return Jim_DeleteCommand(interp, argv[0]);
+ }
+ if (Jim_CompareStringImmediate(interp, argv[1], "read")) {
+ if (redisGetReply(c, (void **)&reply) != REDIS_OK) {
+ reply = NULL;
+ }
+ }
+ else {
+ args = Jim_Alloc(sizeof(*args) * argc - 1);
+ arglens = Jim_Alloc(sizeof(*arglens) * argc - 1);
+ for (i = 1; i < argc; i++) {
+ args[i - 1] = Jim_String(argv[i]);
+ arglens[i - 1] = Jim_Length(argv[i]);
+ }
+ reply = redisCommandArgv(c, argc - 1, args, arglens);
+ Jim_Free(args);
+ Jim_Free(arglens);
+ }
+ /* sometimes commands return NULL */
+ if (reply) {
+ Jim_SetResult(interp, jim_redis_get_result(interp, reply));
+ if (reply->type == REDIS_REPLY_ERROR) {
+ /*Jim_SetResultFormatted(interp, "%#s %#s: %#s", argv[0], argv[1], Jim_GetResult(interp));*/
+ ret = JIM_ERR;
+ }
+ freeReplyObject(reply);
+ }
+ return ret;
+}
+
+static void jim_redis_del_proc(Jim_Interp *interp, void *privData)
+{
+ redisContext *c = privData;
+ JIM_NOTUSED(interp);
+ Jim_DeleteFileHandler(interp, c->fd, JIM_EVENT_READABLE);
+ redisFree(c);
+}
+
+/**
+ * redis <socket-stream>
+ *
+ * Returns a handle that can be used to communicate with the redis
+ * instance over the socket.
+ * The original socket handle is closed.
+ */
+static int jim_redis_cmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ redisContext *c;
+ char buf[60];
+ Jim_Obj *objv[2];
+ long fd;
+ int ret;
+
+ if (argc != 2) {
+ Jim_WrongNumArgs(interp, 1, argv, "socket-stream");
+ return JIM_ERR;
+ }
+
+ /* Invoke getfd to get the file descriptor */
+ objv[0] = argv[1];
+ objv[1] = Jim_NewStringObj(interp, "getfd", -1);
+ ret = Jim_EvalObjVector(interp, 2, objv);
+ if (ret == JIM_OK) {
+ ret = Jim_GetLong(interp, Jim_GetResult(interp), &fd) == JIM_ERR;
+ }
+ if (ret != JIM_OK) {
+ Jim_SetResultFormatted(interp, "%#s: not a valid stream handle: %#s", argv[0], argv[1]);
+ return ret;
+ }
+
+ /* Note that we dup the file descriptor here so that we can close the original */
+ fd = dup(fd);
+ /* Can't fail */
+ c = redisConnectFd(fd);
+ /* Now delete the original stream */
+ Jim_DeleteCommand(interp, argv[1]);
+ snprintf(buf, sizeof(buf), "redis.handle%ld", Jim_GetId(interp));
+ Jim_CreateCommand(interp, buf, jim_redis_subcmd, c, jim_redis_del_proc);
+
+ Jim_SetResult(interp, Jim_MakeGlobalNamespaceName(interp, Jim_NewStringObj(interp, buf, -1)));
+
+ return JIM_OK;
+}
+
+int
+Jim_redisInit(Jim_Interp *interp)
+{
+ if (Jim_PackageProvide(interp, "redis", "1.0", JIM_ERRMSG))
+ return JIM_ERR;
+
+ Jim_CreateCommand(interp, "redis", jim_redis_cmd, NULL, NULL);
+ return JIM_OK;
+}