zsh-workers
 help / color / mirror / code / Atom feed
* Add redis-db module to upstream?
@ 2017-06-03  3:24 Sebastian Gniazdowski
  2017-06-03 17:09 ` Bart Schaefer
  0 siblings, 1 reply; 12+ messages in thread
From: Sebastian Gniazdowski @ 2017-06-03  3:24 UTC (permalink / raw)
  To: zsh-workers

Hello
The module https://github.com/zdharma/zredis is interesting because:

- needs only hiredis package, which is a small modern-C-style library
    - for example it uses sds string library https://github.com/antirez/sds which has a header stored before pointers passed around, making them both C-strings and custom structures (with length stored)

- is well tested – complete coverage in test suite, also separate test suite for testing with Valgrind

- exercises all Zsh data types, so other module creators will have a nice example code

- has a use case – I imagine db administrator binding key "visitors" to Zsh scalar parameter $visitors, to do echo $visitors many times across the day

If we will agree on adding this module I will sent a patch including update of configure.ac. I would still distribute my module, because it can be loaded into Zsh without recompilation of Zsh, and to any Zsh version.

Distributing with Zsh would boost awareness of what Zsh can do. Someone could be inspired by this module if in upstream, while I see it being ignored if only as a plugin.

--
Sebastian Gniazdowski
psprint /at/ zdharma.org


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: Add redis-db module to upstream?
  2017-06-03  3:24 Add redis-db module to upstream? Sebastian Gniazdowski
@ 2017-06-03 17:09 ` Bart Schaefer
  2017-06-03 19:35   ` Daniel Shahaf
  2017-06-04  8:35   ` Sebastian Gniazdowski
  0 siblings, 2 replies; 12+ messages in thread
From: Bart Schaefer @ 2017-06-03 17:09 UTC (permalink / raw)
  To: zsh-workers

I have no objection to including this module's functionality in the base
distribution.  However, I don't think we should just chuck it in there
exactly as-is.

Primarily, we should first decide where this should live in the module
hierarchy, i.e., what name should it have?

For example would it be appropriate to put it under zsh/db/redis and
finally do the work of abstracting ztie out of db_gdbm?  (I know this
supports some operations that the base ztie interface does not.)

In that vein, this also introduces a question of whether zsh/db/ needs
to generically support a password mechanism.  I'd suggest that it use
a parameter, possibly a hash with PM_HIDEVAL set whose keys are chosen
from arguments of ztie, but that's just a first reaction and probably
needs more thought.  Unlike the naming decision, this doesn't need to
be determined before the module is otherwise integrated.

There are probably a few other design questions of this sort that will
arise in due course.


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: Add redis-db module to upstream?
  2017-06-03 17:09 ` Bart Schaefer
@ 2017-06-03 19:35   ` Daniel Shahaf
  2017-06-04  5:57     ` Sebastian Gniazdowski
                       ` (2 more replies)
  2017-06-04  8:35   ` Sebastian Gniazdowski
  1 sibling, 3 replies; 12+ messages in thread
From: Daniel Shahaf @ 2017-06-03 19:35 UTC (permalink / raw)
  To: zsh-workers

Bart Schaefer wrote on Sat, 03 Jun 2017 10:09 -0700:
> I have no objection to including this module's functionality in the base
> distribution.  However, I don't think we should just chuck it in there
> exactly as-is.

This module was created 10 days ago.  It has had no bug reports, no pull
requests, and no code contributors other than its author.  Does it have
any users besides Sebastian?  Will anyone on this list use this module
in his setup?


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: Add redis-db module to upstream?
  2017-06-03 19:35   ` Daniel Shahaf
@ 2017-06-04  5:57     ` Sebastian Gniazdowski
  2017-06-04  6:23       ` Sebastian Gniazdowski
  2017-06-06  2:28     ` Eric Cook
  2017-06-07  9:29     ` Oliver Kiddle
  2 siblings, 1 reply; 12+ messages in thread
From: Sebastian Gniazdowski @ 2017-06-04  5:57 UTC (permalink / raw)
  To: Daniel Shahaf, zsh-workers

On 3 czerwca 2017 at 21:35:52, Daniel Shahaf (d.s@daniel.shahaf.name) wrote:
> This module was created 10 days ago.

It is based on my db/gdbm, which was based on original db/gdbm, so you have two ancestors in the path (there was also Zpopulator, but not released). 176 direct commits, not a weekend-coding with 20 commits.

> It has had no bug reports, no pull requests

No bug reports because like any my project it has more than 200 commits essentially (and I've used tests and Valgrind). E.g. ZUI is 555 direct commits, and probably additional 1000 from ancestors.

> and no code contributors other than its author.

Single-author is a good thing, if focused and engaged. I recall times of XFree86 where single hacker (the one who invented the hacker-sign from game of life?) did Damage extension and multiple others, essentially opening the way to Xorg, IMO. I now recall, it was Keith Packard, his extensions: XRender, XFixes, XDamage, XComposite, XRandR. Hah, it's thanks to him that one can call xrandr and just get current resolution.

Anti-example – universal-ctags has many contributors, but they don't support such a plain thing like multiple arguments to typeset/local/declare. It was a first thing that I've added to my ctags.

> Does it have
> any users besides Sebastian? Will anyone on this list use this module
> in his setup?

It has explicitly *strong* use case. On #redis I meet people who are using python, because redis-cli like most reference-clients is just an inadequate tool when there's actual work to do:

https://walrus.readthedocs.io/en/latest/containers.html#lists

So people open python-cli and manage database by programming. Zsh data structures are much more convenient, sorting, upper-casing etc., searching etc. available via flags, that's not-programming, it's more like just using.

-- 
Sebastian Gniazdowski
psprint /at/ zdharma.org


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: Add redis-db module to upstream?
  2017-06-04  5:57     ` Sebastian Gniazdowski
@ 2017-06-04  6:23       ` Sebastian Gniazdowski
  0 siblings, 0 replies; 12+ messages in thread
From: Sebastian Gniazdowski @ 2017-06-04  6:23 UTC (permalink / raw)
  To: Daniel Shahaf, zsh-workers

Just one more thing – redis is a popular database. It is modern, like sophia:

https://github.com/pmwkaa/sophia

We want Zsh in center of this evolution, because users are in that center. Gdbm is far away from it.

-- 
Sebastian Gniazdowski
psprint /at/ zdharma.org


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: Add redis-db module to upstream?
  2017-06-03 17:09 ` Bart Schaefer
  2017-06-03 19:35   ` Daniel Shahaf
@ 2017-06-04  8:35   ` Sebastian Gniazdowski
  1 sibling, 0 replies; 12+ messages in thread
From: Sebastian Gniazdowski @ 2017-06-04  8:35 UTC (permalink / raw)
  To: Bart Schaefer, zsh-workers

[-- Attachment #1: Type: text/plain, Size: 2817 bytes --]

On 3 czerwca 2017 at 19:09:26, Bart Schaefer (schaefer@brasslantern.com) wrote:
> I have no objection to including this module's functionality in the base
> distribution. However, I don't think we should just chuck it in there
> exactly as-is.
>  
> Primarily, we should first decide where this should live in the module
> hierarchy, i.e., what name should it have?
>  
> For example would it be appropriate to put it under zsh/db/redis and

Done that, sending patch to have basic concretes. Tests are difficult because they require a running redis with no password and databases 10 and 11 available for pruning and filling, etc. The test I send should be somehow disabled by default.

> finally do the work of abstracting ztie out of db_gdbm? (I know this
> supports some operations that the base ztie interface does not.)

The -p option is "passthrough", disables read-cache on the parameter. Gdbm module can easily support this too. Then, -a is for in-arguments password, -A is password from file. It's not perfect that -p isn't password. "a" letter is for "authentication", so basic sanity is kept, but a new mnemonic word for no-cache option to free -p would be best. Cannot come up with any.

Then there is the -f option. -a for "address" would be probably better, but it would harm current gdbm users. However it could be -f OR -a, users will easily learn that it's in general about address, which can be file in some databases. So maybe a clever outcome from this legacy-problems.

For the abstract ztie, I should search builtintab for zrtie and zgtie (a new name for gdbm), and route the call? Earlier I was thinking about some libdl calls to check if a call is available and it was rather a show stopper.

Searching builtintab could also lead to interesting approach – zsh/db module would allow to use custom implementations. Custom plugin-module would provide zrtie or zgtie and routing would work. To support older Zsh, custom module could detect if generic ztie is absent, and provide one. But current ztie isn't abstract, so loading pre-5.3.2 db/gdbm would block this. Rather a minor problem.

> In that vein, this also introduces a question of whether zsh/db/ needs
> to generically support a password mechanism. I'd suggest that it use
> a parameter, possibly a hash with PM_HIDEVAL set whose keys are chosen
> from arguments of ztie, but that's just a first reaction and probably
> needs more thought. Unlike the naming decision, this doesn't need to
> be determined before the module is otherwise integrated.

So this would be a way to block direct zrtie and zgtie usage. It may be a good thing. Like with mkfs: if there wouldn't be mkfs.ext4, people or for sure I would take time to learn generic mkfs syntax.

-- 
Sebastian Gniazdowski
psprint /at/ zdharma.org

[-- Attachment #2: db_redis.diff --]
[-- Type: application/octet-stream, Size: 107732 bytes --]

commit fb4e4dce3bf079803e9498977f0c54433b0b9670
Author: Sebastian Gniazdowski <sgniazdowski@gmail.com>
Date:   Sun Jun 4 09:42:42 2017 +0200

    Redis

diff --git a/Src/Modules/db_redis.c b/Src/Modules/db_redis.c
new file mode 100644
index 0000000..82a1a7f
--- /dev/null
+++ b/Src/Modules/db_redis.c
@@ -0,0 +1,3494 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim:sw=4:sts=4:et
+ */
+
+/*
+ * db_redis.c - bindings for (hi)redis
+ *
+ * Based on db_gdbm.c from Zsh distribution, Copyright (c) 2008 Clint Adams
+ * All rights reserved.
+ *
+ * Copyright (c) 2017 Sebastian Gniazdowski
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ */
+
+#include "db_redis.mdh"
+#include "db_redis.pro"
+
+/* MACROS {{{ */
+#ifndef PM_UPTODATE
+#define PM_UPTODATE     (1<<19) /* Parameter has up-to-date data (e.g. loaded from DB) */
+#endif
+
+#define RESET         "\033[m"
+#define BOLD          "\033[1m"
+#define RED           "\033[31m"
+#define GREEN         "\033[32m"
+#define YELLOW        "\033[33m"
+#define BLUE          "\033[34m"
+#define MAGENTA       "\033[35m"
+#define CYAN          "\033[36m"
+#define BOLD_RED      "\033[1;31m"
+#define BOLD_GREEN    "\033[1;32m"
+#define BOLD_YELLOW   "\033[1;33m"
+#define BOLD_BLUE     "\033[1;34m"
+#define BOLD_MAGENTA  "\033[1;35m"
+#define BOLD_CYAN     "\033[1;36m"
+#define BG_RED        "\033[41m"
+#define BG_GREEN      "\033[42m"
+#define BG_YELLOW     "\033[43m"
+#define BG_BLUE       "\033[44m"
+#define BG_MAGENTA    "\033[45m"
+#define BG_CYAN       "\033[46m"
+
+#define RD_TYPE_UNKNOWN 0
+#define RD_TYPE_STRING 1
+#define RD_TYPE_LIST 2
+#define RD_TYPE_SET 3
+#define RD_TYPE_ZSET 4
+#define RD_TYPE_HASH 5
+/* }}} */
+
+#if defined(HAVE_HIREDIS_HIREDIS_H) && defined(HAVE_REDISCONNECT)
+
+/* DECLARATIONS {{{ */
+static char *type_names[7] = { "invalid", "string", "list", "set", "sorted-set", "hash", "error" };
+
+#include <hiredis/hiredis.h>
+
+static Param createhash(char *name, int flags, int which);
+static int append_tied_name(const char *name);
+static int remove_tied_name(const char *name);
+static char *unmetafy_zalloc(const char *to_copy, int *new_len);
+static void set_length(char *buf, int size);
+static void parse_host_string(const char *input, char *buffer, int size,
+                                char **host, int *port, int *db_index, char **key);
+static int connect(char *nam, redisContext **rc, const char* password, const char *host, int port, int db_index,
+                    const char *resource_name_in);
+static int type(redisContext **rc, const char *redis_host_port, const char *password, char *key, size_t key_len);
+static int is_tied(Param pm);
+static void zrtie_usage();
+static void zrzset_usage();
+static void zruntie_usage();
+static void zredishost_usage();
+static void zredisclear_usage();
+static void myfreeparamnode(HashNode hn);
+static int reconnect(redisContext **rc, const char *hostspec, const char *password);
+static int auth(redisContext **rc, const char *password);
+
+static char *backtype = "db/redis";
+static char *my_nullarray = NULL;
+static int no_database_action = 0;
+/* }}} */
+/* ARRAY: GSU {{{ */
+
+/*
+ * Longer GSU structure, to carry redisContext of owning
+ * database. Every parameter (hash value) receives GSU
+ * pointer and thus also receives redisContext - this way
+ * parameters can access proper database.
+ *
+ * Main HashTable parameter has the same instance of
+ * the custom GSU struct in u.hash->tmpdata field.
+ * When database is closed, `rc` field is set to NULL
+ * and hash values know to not access database when
+ * being unset (total purge at zuntie).
+ *
+ * When database closing is ended, the custom GSU struct
+ * is freed. Only new ztie creates new custom GSU struct
+ * instance.
+ *
+ * This is also for hashes, and is called *scalar*, because
+ * it's for hash *elements*, PM_SCALAR | PM_HASHELEM. Also
+ * for strings.
+ */
+struct gsu_scalar_ext {
+    struct gsu_scalar std;
+    int use_cache;
+    redisContext *rc;
+    char *redis_host_port;
+    char *key;
+    size_t key_len;
+    char *password;
+};
+
+/* Used by sets */
+struct gsu_array_ext {
+    struct gsu_array std;
+    int use_cache;
+    redisContext *rc;
+    char *redis_host_port;
+    char *key;
+    size_t key_len;
+    char *password;
+};
+
+/* Source structure - will be copied to allocated one,
+ * with `rc` filled. `rc` allocation <-> gsu allocation. */
+static const struct gsu_scalar_ext hashel_gsu_ext =
+    { { redis_getfn, redis_setfn, redis_unsetfn }, 0, 0, 0, 0, 0 };
+
+/* Hash GSU, normal one */
+static const struct gsu_hash redis_hash_gsu =
+    { hashgetfn, redis_hash_setfn, redis_hash_unsetfn };
+
+/* String (scalar to key) mapping */
+static const struct gsu_scalar_ext string_gsu_ext =
+    { { redis_str_getfn, redis_str_setfn, redis_str_unsetfn }, 0, 0, 0, 0, 0 };
+
+/* Array to set mapping */
+static const struct gsu_array_ext arrset_gsu_ext =
+    { { redis_arrset_getfn, redis_arrset_setfn, redis_arrset_unsetfn }, 0, 0, 0, 0, 0 };
+
+/* PM_HASHELEM GSU for zset */
+static const struct gsu_scalar_ext hashel_zset_gsu_ext =
+    { { redis_zset_getfn, redis_zset_setfn, redis_zset_unsetfn }, 0, 0, 0, 0, 0 };
+
+/* Hash GSU for zset, normal one */
+static const struct gsu_hash hash_zset_gsu =
+    { hashgetfn, redis_hash_zset_setfn, redis_hash_zset_unsetfn };
+
+/* PM_HASHELEM GSU for hset */
+static const struct gsu_scalar_ext hashel_hset_gsu_ext =
+    { { redis_hset_getfn, redis_hset_setfn, redis_hset_unsetfn }, 0, 0, 0, 0, 0 };
+
+/* Hash GSU for hset, normal one */
+static const struct gsu_hash hash_hset_gsu =
+    { hashgetfn, redis_hash_hset_setfn, redis_hash_hset_unsetfn };
+
+/* Array to list mapping */
+static const struct gsu_array_ext arrlist_gsu_ext =
+    { { redis_arrlist_getfn, redis_arrlist_setfn, redis_arrlist_unsetfn }, 0, 0, 0, 0, 0 };
+
+/* }}} */
+/* ARRAY: builtin {{{ */
+static struct builtin bintab[] = {
+    BUILTIN("zrtie", 0, bin_zrtie, 0, -1, 0, "d:f:rpha:A:", NULL),
+    BUILTIN("zruntie", 0, bin_zruntie, 0, -1, 0, "uh", NULL),
+    BUILTIN("zredishost", 0, bin_zredishost, 0, -1, 0, "h", NULL),
+    BUILTIN("zredisclear", 0, bin_zredisclear, 0, 2, 0, "h", NULL),
+    BUILTIN("zrzset", 0, bin_zrzset, 0, 1, 0, "h", NULL),
+};
+/* }}} */
+/* ARRAY: other {{{ */
+#define ROARRPARAMDEF(name, var) \
+    { name, PM_ARRAY | PM_READONLY, (void *) var, NULL,  NULL, NULL, NULL }
+
+/* Holds names of all tied parameters */
+char **zredis_tied;
+
+static struct paramdef patab[] = {
+    ROARRPARAMDEF("zredis_tied", &zredis_tied),
+};
+/* }}} */
+
+/* FUNCTION: bin_zrtie {{{ */
+/**/
+static int
+bin_zrtie(char *nam, char **args, Options ops, UNUSED(int func))
+{
+    redisContext *rc = NULL;
+    int read_write = 1, pmflags = PM_REMOVABLE;
+    Param tied_param;
+
+    /* Check options */
+
+    if (OPT_ISSET(ops,'h')) {
+        zrtie_usage();
+        return 0;
+    }
+
+    if (!OPT_ISSET(ops,'d')) {
+        zwarnnam(nam, "you must pass `-d %s', see `-h'", backtype);
+        return 1;
+    }
+    if (!OPT_ISSET(ops,'f')) {
+        zwarnnam(nam, "you must pass `-f' with {host}[:port][/[db_idx][/key]], see `-h'", NULL);
+        return 1;
+    }
+    if (OPT_ISSET(ops,'r')) {
+        read_write = 0;
+        pmflags |= PM_READONLY;
+    }
+
+    if (strcmp(OPT_ARG(ops, 'd'), backtype) != 0) {
+        zwarnnam(nam, "unsupported backend type `%s', see `-h'", OPT_ARG(ops, 'd'));
+        return 1;
+    }
+
+    /* Parse host data */
+
+    char *pmname, *resource_name_in, resource_name[192];
+    char *host="127.0.0.1", *key="", *password = NULL;
+    int port = 6379, db_index = 0;
+
+    resource_name_in = OPT_ARG(ops, 'f');
+    pmname = *args;
+
+    if (!pmname) {
+        zwarnnam(nam, "you must pass non-option argument - the target parameter to create, see -h", backtype);
+        return 1;
+    }
+
+    parse_host_string(resource_name_in, resource_name, 192, &host, &port, &db_index, &key);
+
+    /* Unset existing parameter */
+
+    if ((tied_param = (Param)paramtab->getnode(paramtab, pmname)) && !(tied_param->node.flags & PM_UNSET)) {
+        if (is_tied(tied_param)) {
+            zwarnnam(nam, "Refusing to re-tie already tied parameter `%s'", pmname);
+            zwarnnam(nam, "The involved `unset' could clear the database-part handled by `%s'", pmname);
+            return 1;
+        }
+        /*
+         * Unset any existing parameter. Note there's no implicit
+         * "local" here, but if the existing parameter is local
+         * then new parameter will be also local without following
+         * unset.
+         *
+         * We need to do this before attempting to open the DB
+         * in case this variable is already tied to a DB.
+         *
+         * This can fail if the variable is readonly or restricted.
+         * We could call unsetparam() and check errflag instead
+         * of the return status.
+         */
+        if (unsetparam_pm(tied_param, 0, 1))
+            return 1;
+    }
+
+    /* Connect */
+
+    char buf[1025];
+    if (OPT_ISSET(ops,'a')) {
+        password = OPT_ARG(ops, 'a');
+    } else if (OPT_ISSET(ops, 'A')) {
+        const char *pfile_path = OPT_ARG(ops,'A');
+        FILE *pfile = fopen(pfile_path, "r");
+        if (pfile) {
+            int size = fread(buf, 1, 1024, pfile);
+            fclose(pfile);
+            if (size > 0) {
+                if (size > 1024)
+                    size = 1024;
+                while( buf[size-1] == '\r' || buf[size-1] == '\n' ) {
+                    --size;
+                }
+                buf[size] = '\0';
+                password = buf;
+            } else {
+                zwarnnam(nam, "Couldn't read password file: `%s', aborting", pfile_path);
+                return 1;
+            }
+        } else {
+            zwarnnam(nam, "Couldn't open password file: `%s', aborting", pfile_path);
+            return 1;
+        }
+    }
+
+    if(!connect(nam, &rc, password, host, port, db_index, resource_name_in)) {
+        return 1;
+    }
+
+    /* Main string storage? */
+    if (0 == strcmp(key,"")) {
+        /* Create hash */
+        if (!(tied_param = createhash(pmname, pmflags, 0))) {
+            zwarnnam(nam, "cannot create the requested hash parameter: %s", pmname);
+            redisFree(rc);
+            return 1;
+        }
+
+        /* Allocate parameter sub-gsu, fill rc field.
+         * rc allocation is 1 to 1 accompanied by
+         * gsu_scalar_ext allocation. */
+
+        struct gsu_scalar_ext *rc_carrier = NULL;
+        rc_carrier = (struct gsu_scalar_ext *) zalloc(sizeof(struct gsu_scalar_ext));
+        rc_carrier->std = hashel_gsu_ext.std;
+        rc_carrier->use_cache = 1;
+        if (OPT_ISSET(ops,'p'))
+            rc_carrier->use_cache = 0;
+        rc_carrier->rc = rc;
+
+        /* Fill also host:port// and password fields */
+        rc_carrier->redis_host_port = ztrdup(resource_name_in);
+        if (password)
+            rc_carrier->password = ztrdup(password);
+        else
+            rc_carrier->password = NULL;
+
+        tied_param->u.hash->tmpdata = (void *)rc_carrier;
+        tied_param->gsu.h = &redis_hash_gsu;
+    } else {
+        int tpe = type(&rc, resource_name_in, password, key, (size_t) strlen(key));
+        if (tpe == RD_TYPE_STRING) {
+            if (!(tied_param = createparam(pmname, pmflags | PM_SPECIAL))) {
+                zwarnnam(nam, "cannot create the requested scalar parameter: %s", pmname);
+                redisFree(rc);
+                return 1;
+            }
+            struct gsu_scalar_ext *rc_carrier = NULL;
+            rc_carrier = (struct gsu_scalar_ext *) zalloc(sizeof(struct gsu_scalar_ext));
+            rc_carrier->std = string_gsu_ext.std;
+            rc_carrier->use_cache = 1;
+            if (OPT_ISSET(ops,'p'))
+                rc_carrier->use_cache = 0;
+            rc_carrier->rc = rc;
+            rc_carrier->key = ztrdup(key);
+            rc_carrier->key_len = strlen(key);
+
+            /* Fill also host:port// and password fields */
+            rc_carrier->redis_host_port = ztrdup(resource_name_in);
+            if (password)
+                rc_carrier->password = ztrdup(password);
+            else
+                rc_carrier->password = NULL;
+
+            tied_param->gsu.s = (GsuScalar) rc_carrier;
+        } else if (tpe == RD_TYPE_SET) {
+            if (!(tied_param = createparam(pmname, pmflags | PM_ARRAY | PM_SPECIAL))) {
+                zwarnnam(nam, "cannot create the requested array (for set) parameter: %s", pmname);
+                redisFree(rc);
+                return 1;
+            }
+            struct gsu_array_ext *rc_carrier = NULL;
+            rc_carrier = (struct gsu_array_ext *) zalloc(sizeof(struct gsu_array_ext));
+            rc_carrier->std = arrset_gsu_ext.std;
+            rc_carrier->use_cache = 1;
+            if (OPT_ISSET(ops,'p'))
+                rc_carrier->use_cache = 0;
+            rc_carrier->rc = rc;
+            rc_carrier->key = ztrdup(key);
+            rc_carrier->key_len = strlen(key);
+
+            /* Fill also host:port// and password fields */
+            rc_carrier->redis_host_port = ztrdup(resource_name_in);
+            if (password)
+                rc_carrier->password = ztrdup(password);
+            else
+                rc_carrier->password = NULL;
+
+            tied_param->gsu.s = (GsuScalar) rc_carrier;
+        } else if (tpe == RD_TYPE_ZSET) {
+            /* Create hash */
+            if (!(tied_param = createhash(pmname, pmflags, 1))) {
+                zwarnnam(nam, "cannot create the requested hash (for zset) parameter: %s", pmname);
+                redisFree(rc);
+                return 1;
+            }
+
+            struct gsu_scalar_ext *rc_carrier = NULL;
+            rc_carrier = (struct gsu_scalar_ext *) zalloc(sizeof(struct gsu_scalar_ext));
+            rc_carrier->std = hashel_zset_gsu_ext.std;
+            rc_carrier->use_cache = 1;
+            if (OPT_ISSET(ops,'p'))
+                rc_carrier->use_cache = 0;
+            rc_carrier->rc = rc;
+            rc_carrier->key = ztrdup(key);
+            rc_carrier->key_len = strlen(key);
+
+            /* Fill also host:port// and password fields */
+            rc_carrier->redis_host_port = ztrdup(resource_name_in);
+            if (password)
+                rc_carrier->password = ztrdup(password);
+            else
+                rc_carrier->password = NULL;
+
+            tied_param->u.hash->tmpdata = (void *)rc_carrier;
+            tied_param->gsu.h = &hash_zset_gsu;
+        } else if (tpe == RD_TYPE_HASH) {
+            /* Create hash */
+            if (!(tied_param = createhash(pmname, pmflags, 2))) {
+                zwarnnam(nam, "cannot create the requested hash (for hset) parameter: %s", pmname);
+                redisFree(rc);
+                return 1;
+            }
+
+            struct gsu_scalar_ext *rc_carrier = NULL;
+            rc_carrier = (struct gsu_scalar_ext *) zalloc(sizeof(struct gsu_scalar_ext));
+            rc_carrier->std = hashel_hset_gsu_ext.std;
+            rc_carrier->use_cache = 1;
+            if (OPT_ISSET(ops,'p'))
+                rc_carrier->use_cache = 0;
+            rc_carrier->rc = rc;
+            rc_carrier->key = ztrdup(key);
+            rc_carrier->key_len = strlen(key);
+
+            /* Fill also host:port// and password fields */
+            rc_carrier->redis_host_port = ztrdup(resource_name_in);
+            if (password)
+                rc_carrier->password = ztrdup(password);
+            else
+                rc_carrier->password = NULL;
+
+            tied_param->u.hash->tmpdata = (void *)rc_carrier;
+            tied_param->gsu.h = &hash_hset_gsu;
+        } else if (tpe == RD_TYPE_LIST) {
+            if (!(tied_param = createparam(pmname, pmflags | PM_ARRAY | PM_SPECIAL))) {
+                zwarnnam(nam, "cannot create the requested array (for list) parameter: %s", pmname);
+                redisFree(rc);
+                return 1;
+            }
+            struct gsu_array_ext *rc_carrier = NULL;
+            rc_carrier = (struct gsu_array_ext *) zalloc(sizeof(struct gsu_array_ext));
+            rc_carrier->std = arrlist_gsu_ext.std;
+            rc_carrier->use_cache = 1;
+            if (OPT_ISSET(ops,'p'))
+                rc_carrier->use_cache = 0;
+            rc_carrier->rc = rc;
+            rc_carrier->key = ztrdup(key);
+            rc_carrier->key_len = strlen(key);
+
+            /* Fill also host:port// and password fields */
+            rc_carrier->redis_host_port = ztrdup(resource_name_in);
+            if (password)
+                rc_carrier->password = ztrdup(password);
+            else
+                rc_carrier->password = NULL;
+
+            tied_param->gsu.s = (GsuScalar) rc_carrier;
+        } else {
+            redisFree(rc);
+            zwarnnam(nam, "Unknown key type: %s", type_names[tpe]);
+            return 1;
+        }
+    }
+
+    /* Save in tied-enumeration array */
+    append_tied_name(pmname);
+
+    return 0;
+}
+/* }}} */
+/* FUNCTION: bin_zruntie {{{ */
+/**/
+static int
+bin_zruntie(char *nam, char **args, Options ops, UNUSED(int func))
+{
+    Param pm;
+    char *pmname;
+    int ret = 0;
+
+    if (OPT_ISSET(ops,'h')) {
+        zruntie_usage();
+        return 0;
+    }
+
+    if (!*args) {
+        zwarnnam(nam, "At least one variable name is needed, see -h");
+        return 1;
+    }
+
+    for (pmname = *args; *args++; pmname = *args) {
+        /* Get param */
+        pm = (Param) paramtab->getnode(paramtab, pmname);
+        if(!pm) {
+            zwarnnam(nam, "cannot untie `%s', parameter not found", pmname);
+            ret = 1;
+            continue;
+        }
+
+        if (pm->gsu.h == &redis_hash_gsu) {
+            queue_signals();
+            if (OPT_ISSET(ops,'u'))
+                pm->node.flags &= ~PM_READONLY;
+            if (unsetparam_pm(pm, 0, 1)) {
+                /* assume already reported */
+                ret = 1;
+            }
+            unqueue_signals();
+        } else if (pm->gsu.s->getfn == &redis_str_getfn) {
+            if (pm->node.flags & PM_READONLY && !OPT_ISSET(ops,'u')) {
+                zwarnnam(nam, "cannot untie `%s', parameter is read only, use -u option", pmname);
+                continue;
+            }
+            pm->node.flags &= ~PM_READONLY;
+
+            queue_signals();
+            /* Detach from database, untie doesn't clear the database */
+            redis_str_untie(pm);
+
+            if (unsetparam_pm(pm, 0, 1)) {
+                /* assume already reported */
+                ret = 1;
+            }
+            unqueue_signals();
+        } else if (pm->gsu.a->getfn == &redis_arrset_getfn) {
+            if (pm->node.flags & PM_READONLY && !OPT_ISSET(ops,'u')) {
+                zwarnnam(nam, "cannot untie array `%s', the set-bound parameter is read only, use -u option", pmname);
+                continue;
+            }
+            pm->node.flags &= ~PM_READONLY;
+            queue_signals();
+            /* Detach from database, untie doesn't clear the database */
+            redis_arrset_untie(pm);
+
+            if (unsetparam_pm(pm, 0, 1)) {
+                /* assume already reported */
+                ret = 1;
+            }
+            unqueue_signals();
+        } else if (pm->gsu.h == &hash_zset_gsu) {
+            queue_signals();
+            if (OPT_ISSET(ops,'u'))
+                pm->node.flags &= ~PM_READONLY;
+            if (unsetparam_pm(pm, 0, 1)) {
+                /* assume already reported */
+                ret = 1;
+            }
+            unqueue_signals();
+        } else if (pm->gsu.h == &hash_hset_gsu) {
+            queue_signals();
+            if (OPT_ISSET(ops,'u'))
+                pm->node.flags &= ~PM_READONLY;
+            if (unsetparam_pm(pm, 0, 1)) {
+                /* assume already reported */
+                ret = 1;
+            }
+            unqueue_signals();
+        } else if (pm->gsu.a->getfn == &redis_arrlist_getfn) {
+            if (pm->node.flags & PM_READONLY && !OPT_ISSET(ops,'u')) {
+                zwarnnam(nam, "cannot untie array `%s', the list-bound parameter is read only, use -u option", pmname);
+                continue;
+            }
+            pm->node.flags &= ~PM_READONLY;
+            queue_signals();
+            /* Detach from database, untie doesn't clear the database */
+            redis_arrset_untie(pm);
+
+            if (unsetparam_pm(pm, 0, 1)) {
+                ret = 1;
+            }
+            unqueue_signals();
+        } else {
+            zwarnnam(nam, "not a tied redis parameter: `%s'", pmname);
+            ret = 1;
+            continue;
+        }
+    }
+
+    return ret;
+}
+/* }}} */
+/* FUNCTION: bin_zredishost {{{ */
+/**/
+static int
+bin_zredishost(char *nam, char **args, Options ops, UNUSED(int func))
+{
+    Param pm;
+    char *pmname;
+
+    if (OPT_ISSET(ops, 'h')) {
+        zredishost_usage();
+        return 0;
+    }
+
+    pmname = *args;
+
+    if (!pmname) {
+        zwarnnam(nam, "parameter name (whose host-spec is to be written to $REPLY) is required, see -h");
+        return 1;
+    }
+
+    pm = (Param) paramtab->getnode(paramtab, pmname);
+    if(!pm) {
+        zwarnnam(nam, "no such parameter: %s", pmname);
+        return 1;
+    }
+
+    const char *hostspec = NULL;
+
+    if (pm->gsu.h == &redis_hash_gsu) {
+        /* Paranoia, it *will* be always set */
+        hostspec = ((struct gsu_scalar_ext *)pm->u.hash->tmpdata)->redis_host_port;
+    } else if(pm->gsu.s->getfn == &redis_str_getfn) {
+        hostspec = ((struct gsu_scalar_ext *)pm->gsu.s)->redis_host_port;
+    } else if(pm->gsu.a->getfn == &redis_arrset_getfn) {
+        hostspec = ((struct gsu_array_ext *)pm->gsu.a)->redis_host_port;
+    } else if(pm->gsu.h == &hash_zset_gsu) {
+        hostspec = ((struct gsu_scalar_ext *)pm->u.hash->tmpdata)->redis_host_port;
+    } else if(pm->gsu.h == &hash_hset_gsu) {
+        hostspec = ((struct gsu_scalar_ext *)pm->u.hash->tmpdata)->redis_host_port;
+    } else if(pm->gsu.a->getfn == &redis_arrlist_getfn) {
+        hostspec = ((struct gsu_array_ext *)pm->gsu.a)->redis_host_port;
+    } else {
+        zwarnnam(nam, "not a tied zredis parameter: `%s', REPLY unchanged", pmname);
+        return 1;
+    }
+
+    if (hostspec) {
+        setsparam("REPLY", ztrdup(hostspec));
+    } else {
+        setsparam("REPLY", ztrdup(""));
+    }
+    return 0;
+}
+/* }}} */
+/* FUNCTION: bin_zredisclear {{{ */
+/**/
+static int
+bin_zredisclear(char *nam, char **args, Options ops, UNUSED(int func))
+{
+    Param pm;
+    char *pmname, *key;
+
+    pmname = *args++;
+    key = *args;
+
+    if (OPT_ISSET(ops,'h')) {
+        zredisclear_usage();
+        return 0;
+    }
+
+    if (!pmname) {
+        zwarnnam(nam, "parameter name (whose cache is to be cleared) is required, see -h");
+        return 1;
+    }
+
+    pm = (Param) paramtab->getnode(paramtab, pmname);
+    if (!pm) {
+        zwarnnam(nam, "no such parameter: %s", pmname);
+        return 1;
+    }
+
+    if (pm->gsu.h == &redis_hash_gsu) {
+        if (!key) {
+            zwarnnam(nam, "Key name, which is to be cache-cleared in hash `%s', is required", pmname);
+            return 1;
+        }
+        HashTable ht = pm->u.hash;
+        HashNode hn = gethashnode2(ht, key);
+        Param val_pm = (Param) hn;
+        if (val_pm) {
+            val_pm->node.flags &= ~(PM_UPTODATE);
+        }
+    } else if (pm->gsu.s->getfn == &redis_str_getfn) {
+        pm->node.flags &= ~(PM_UPTODATE);
+        if (key)
+            zwarnnam(nam, "Ignored argument `%s'", key);
+    } else if (pm->gsu.a->getfn == &redis_arrset_getfn) {
+        pm->node.flags &= ~(PM_UPTODATE);
+        if (key)
+            zwarnnam(nam, "Ignored argument `%s'", key);
+    } else if(pm->gsu.h == &hash_zset_gsu) {
+        if (!key) {
+            zwarnnam(nam, "Key name, which is to be cache-cleared in hash/zset `%s', is required", pmname);
+            return 1;
+        }
+        HashTable ht = pm->u.hash;
+        HashNode hn = gethashnode2(ht, key);
+        Param val_pm = (Param) hn;
+        if (val_pm) {
+            val_pm->node.flags &= ~(PM_UPTODATE);
+        }
+    } else if(pm->gsu.h == &hash_hset_gsu) {
+        if (!key) {
+            zwarnnam(nam, "Key name, which is to be cache-cleared in hash/hset `%s', is required", pmname);
+            return 1;
+        }
+        HashTable ht = pm->u.hash;
+        HashNode hn = gethashnode2(ht, key);
+        Param val_pm = (Param) hn;
+        if (val_pm) {
+            val_pm->node.flags &= ~(PM_UPTODATE);
+        }
+    } else if (pm->gsu.a->getfn == &redis_arrlist_getfn) {
+        pm->node.flags &= ~(PM_UPTODATE);
+        if (key)
+            zwarnnam(nam, "Ignored argument `%s'", key);
+    } else {
+        zwarnnam(nam, "not a tied zredis parameter: %s", pmname);
+        return 1;
+    }
+
+    return 0;
+}
+/* }}} */
+
+/*************** HASH ELEM ***************/
+
+/* FUNCTION: redis_getfn {{{ */
+
+/*
+ * The param is actual param in hash – always, because
+ * redis_get_node creates every new key seen. However, it
+ * might be not PM_UPTODATE - which means that database
+ * wasn't yet queried.
+ *
+ * It will be left in this state if database doesn't
+ * contain such key. That might be a drawback, maybe
+ * setting to empty value has sense. This would remove
+ * subtle hcalloc(1) leak.
+ */
+
+/**/
+static char *
+redis_getfn(Param pm)
+{
+    struct gsu_scalar_ext *gsu_ext;
+    char *key;
+    size_t key_len;
+    redisContext *rc;
+    redisReply *reply;
+
+    gsu_ext = (struct gsu_scalar_ext *) pm->gsu.s;
+
+    /* Key already retrieved? */
+    if ((pm->node.flags & PM_UPTODATE) && gsu_ext->use_cache) {
+        return pm->u.str ? pm->u.str : (char *) hcalloc(1);
+    }
+
+    /* Unmetafy key. Redis fits nice into this
+     * process, as it can use length of data */
+    int umlen = 0;
+    char *umkey = unmetafy_zalloc(pm->node.nam, &umlen);
+
+    key = umkey;
+    key_len = umlen;
+
+    int retry = 0;
+ retry:
+    rc = gsu_ext->rc;
+
+    if (rc) {
+        reply = redisCommand(rc, "EXISTS %b", key, (size_t) key_len);
+        if (reply && reply->type == REDIS_REPLY_INTEGER && reply->integer == 1) {
+            freeReplyObject(reply);
+
+            reply = redisCommand(rc, "GET %b", key, (size_t) key_len);
+            if (reply && reply->type == REDIS_REPLY_STRING) {
+                /* We have data – store it, return it */
+                pm->node.flags |= PM_UPTODATE;
+
+                /* Ensure there's no leak */
+                if (pm->u.str) {
+                    zsfree(pm->u.str);
+                    pm->u.str = NULL;
+                }
+
+                /* Metafy returned data. All fits - metafy
+                 * can obtain data length to avoid using \0 */
+                pm->u.str = metafy(reply->str, reply->len, META_DUP);
+                freeReplyObject(reply);
+
+                /* Free key, restoring its original length */
+                set_length(umkey, key_len);
+                zsfree(umkey);
+
+                /* Can return pointer, correctly saved inside hash */
+                return pm->u.str;
+            } else if (reply) {
+                freeReplyObject(reply);
+            }
+        } else if (reply) {
+            freeReplyObject(reply);
+        }
+
+        if (!retry && (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
+            retry = 1;
+            if(reconnect(&gsu_ext->rc, gsu_ext->redis_host_port, gsu_ext->password))
+                goto retry;
+        } else if (retry && (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
+            zwarn("Aborting (no connection)");
+        }
+    }
+
+    /* Free key, restoring its original length */
+    set_length(umkey, key_len);
+    zsfree(umkey);
+
+    return "";
+}
+/* }}} */
+/* FUNCTION: redis_setfn {{{ */
+/**/
+static void
+redis_setfn(Param pm, char *val)
+{
+    char *key, *content;
+    size_t key_len, content_len;
+    redisContext *rc;
+    redisReply *reply;
+    struct gsu_scalar_ext *gsu_ext;
+
+    gsu_ext = (struct gsu_scalar_ext *) pm->gsu.s;
+
+    /* Set is done on parameter and on database. */
+
+    /* Parameter */
+    if (pm->u.str) {
+        zsfree(pm->u.str);
+        pm->u.str = NULL;
+        pm->node.flags &= ~(PM_UPTODATE);
+    }
+
+    if (val) {
+        pm->u.str = ztrdup(val);
+        pm->node.flags |= PM_UPTODATE;
+    }
+
+    int retry = 0;
+ retry:
+
+    /* Database */
+    rc = gsu_ext->rc;
+
+    /* Can be NULL, when calling unset after untie */
+    if (rc && no_database_action == 0) {
+        int umlen = 0;
+        char *umkey = unmetafy_zalloc(pm->node.nam, &umlen);
+
+        key = umkey;
+        key_len = umlen;
+
+        if (val) {
+            /* Unmetafy with exact zalloc size */
+            char *umval = unmetafy_zalloc(val, &umlen);
+
+            /* Store */
+            content = umval;
+            content_len = umlen;
+            reply = redisCommand(rc, "SET %b %b", key, (size_t) key_len, content, (size_t) content_len);
+            if (reply)
+                freeReplyObject(reply);
+
+            /* Free */
+            set_length(umval, content_len);
+            zsfree(umval);
+        } else {
+            reply = redisCommand(rc, "DEL %b", key, (size_t) key_len);
+            if (reply)
+                freeReplyObject(reply);
+        }
+
+        /* Free key */
+        set_length(umkey, key_len);
+        zsfree(umkey);
+
+        if (!retry && (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
+            retry = 1;
+            if(reconnect(&gsu_ext->rc, gsu_ext->redis_host_port, gsu_ext->password))
+                goto retry;
+        } else if (retry && (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
+            zwarn("Aborting (no connection)");
+        }
+    }
+}
+/* }}} */
+/* FUNCTION: redis_unsetfn {{{ */
+/**/
+static void
+redis_unsetfn(Param pm, UNUSED(int um))
+{
+    /* Set with NULL */
+    redis_setfn(pm, NULL);
+}
+/* }}} */
+
+/***************** HASH ******************/
+
+/* FUNCTION: redis_get_node {{{ */
+/**/
+static HashNode
+redis_get_node(HashTable ht, const char *name)
+{
+    HashNode hn = gethashnode2(ht, name);
+    Param val_pm = (Param) hn;
+
+    /* Entry for key doesn't exist? Create it now,
+     * it will be interfacing between the database
+     * and Zsh - through special gsu. So, any seen
+     * key results in new interfacing parameter.
+     *
+     * Add the Param to its hash, it is not PM_UPTODATE.
+     * It will be loaded from database *and filled*
+     * or left in that state if the database doesn't
+     * contain it.
+     */
+
+    if (!val_pm) {
+        val_pm = (Param) zshcalloc(sizeof (*val_pm));
+        val_pm->node.flags = PM_SCALAR | PM_HASHELEM; /* no PM_UPTODATE */
+        val_pm->gsu.s = (GsuScalar) ht->tmpdata;
+        ht->addnode(ht, ztrdup(name), val_pm); // sets pm->node.nam
+    }
+
+    return (HashNode) val_pm;
+}
+/* }}} */
+/* FUNCTION: scan_keys {{{ */
+/**/
+static void
+scan_keys(HashTable ht, ScanFunc func, int flags)
+{
+    char *key;
+    size_t key_len;
+    redisContext *rc;
+    redisReply *reply;
+    struct gsu_scalar_ext *gsu_ext;
+
+    gsu_ext = (struct gsu_scalar_ext *)ht->tmpdata;
+
+    int retry = 0;
+ retry:
+    rc = gsu_ext->rc;
+
+    if (!rc)
+        return;
+
+    /* Iterate keys adding them to hash, so
+     * we have Param to use in `func` */
+    reply = redisCommand(rc, "KEYS *");
+
+    /* Disconnect detection */
+    if (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF)) {
+        if (reply)
+            freeReplyObject(reply);
+        if (retry) {
+            zwarn("Aborting (no connection)");
+            return;
+        }
+        retry = 1;
+        if(reconnect(&gsu_ext->rc, gsu_ext->redis_host_port, gsu_ext->password))
+            goto retry;
+        else
+            return;
+    }
+
+    if (!reply || reply->type != REDIS_REPLY_ARRAY) {
+        zwarn("Incorrect reply from redis command KEYS, aborting");
+        if (reply)
+            freeReplyObject(reply);
+        return;
+    }
+
+    for (size_t j = 0; j < reply->elements; j++) {
+        redisReply *entry = reply->element[j];
+        if (entry == NULL || entry->type != REDIS_REPLY_STRING) {
+            continue;
+        }
+
+        key = entry->str;
+        key_len = entry->len;
+
+        /* Only scan string keys, ignore the rest (hashes, sets, etc.) */
+        if (RD_TYPE_STRING != type(&gsu_ext->rc, gsu_ext->redis_host_port, gsu_ext->password, key, (size_t) key_len)) {
+            rc = gsu_ext->rc;
+            continue;
+        }
+        rc = gsu_ext->rc;
+
+        /* This returns database-interfacing Param,
+         * it will return u.str or first fetch data
+         * if not PM_UPTODATE (newly created) */
+        char *zkey = metafy(key, key_len, META_DUP);
+        HashNode hn = redis_get_node(ht, zkey);
+        zsfree(zkey);
+
+        func(hn, flags);
+    }
+
+    freeReplyObject(reply);
+}
+/* }}} */
+/* FUNCTION: redis_hash_setfn {{{ */
+
+/*
+ * Replace database with new hash
+ */
+
+/**/
+static void
+redis_hash_setfn(Param pm, HashTable ht)
+{
+    size_t i, j;
+    HashNode hn;
+    char *key, *content;
+    size_t key_len, content_len;
+    redisContext *rc;
+    redisReply *reply, *entry, *reply2;
+    struct gsu_scalar_ext *gsu_ext;
+
+    if (!pm->u.hash || pm->u.hash == ht)
+        return;
+
+    gsu_ext = (struct gsu_scalar_ext *)pm->u.hash->tmpdata;
+
+    int retry = 0;
+ retry:
+    rc = gsu_ext->rc;
+
+    if (!rc)
+        return;
+
+    /* KEYS */
+    reply = redisCommand(rc, "KEYS *");
+    if (reply == NULL || reply->type != REDIS_REPLY_ARRAY) {
+        if (reply)
+            freeReplyObject(reply);
+        goto do_retry;
+    }
+
+    for (j = 0; j < reply->elements; j++) {
+        entry = reply->element[j];
+        if (entry == NULL || entry->type != REDIS_REPLY_STRING) {
+            continue;
+        }
+        key = entry->str;
+        key_len = entry->len;
+
+        /* Only scan string keys, ignore the rest (hashes, sets, etc.) */
+        if (RD_TYPE_STRING != type(&gsu_ext->rc, gsu_ext->redis_host_port, gsu_ext->password, key, (size_t) key_len)) {
+            rc = gsu_ext->rc;
+            continue;
+        }
+        rc = gsu_ext->rc;
+
+        queue_signals();
+
+        /* DEL */
+        reply2 = redisCommand(rc, "DEL %b", key, (size_t) key_len);
+        if (reply2) {
+            freeReplyObject(reply2);
+            reply2 = NULL;
+        }
+        if (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF))
+            break;
+
+        unqueue_signals();
+    }
+    freeReplyObject(reply);
+
+ do_retry:
+    if (!retry && (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
+        retry = 1;
+        if(reconnect(&gsu_ext->rc, gsu_ext->redis_host_port, gsu_ext->password))
+            goto retry;
+        else
+            return;
+    } else if (retry && (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
+        zwarn("Aborting (no connection)");
+        return;
+    }
+
+    no_database_action = 1;
+    emptyhashtable(pm->u.hash);
+    no_database_action = 0;
+
+    if (!ht)
+        return;
+
+     /* Put new strings into database, having
+      * their interfacing-Params created */
+
+    retry = 0;
+ retry2:
+    rc = gsu_ext->rc;
+
+    for (i = 0; i < ht->hsize; i++) {
+        for (hn = ht->nodes[i]; hn; hn = hn->next) {
+            struct value v;
+
+            v.isarr = v.flags = v.start = 0;
+            v.end = -1;
+            v.arr = NULL;
+            v.pm = (Param) hn;
+
+            /* Unmetafy key */
+            int umlen = 0;
+            char *umkey = unmetafy_zalloc(v.pm->node.nam, &umlen);
+
+            key = umkey;
+            key_len = umlen;
+
+            queue_signals();
+
+            /* Unmetafy data */
+            char *umval = unmetafy_zalloc(getstrvalue(&v), &umlen);
+
+            content = umval;
+            content_len = umlen;
+
+            /* SET */
+            reply = redisCommand(rc, "SET %b %b", key, (size_t) key_len, content, (size_t) content_len);
+            if (reply)
+                freeReplyObject(reply);
+
+            /* Free, restoring original length */
+            set_length(umval, content_len);
+            zsfree(umval);
+            set_length(umkey, key_len);
+            zsfree(umkey);
+
+            unqueue_signals();
+        }
+
+        /* Disconnect detection */
+        if (!retry && (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
+            retry = 1;
+            if(reconnect(&gsu_ext->rc, gsu_ext->redis_host_port, gsu_ext->password))
+                goto retry2;
+            else
+                return;
+        } else if (retry && (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
+            zwarn("Aborting (no connection)");
+            return;
+        }
+    }
+}
+/* }}} */
+/* FUNCTION: redis_hash_unsetfn {{{ */
+/**/
+static void
+redis_hash_unsetfn(Param pm, UNUSED(int exp))
+{
+    /* This will make database contents survive the
+     * unset, as standard GSU will be put in place */
+    redis_hash_untie(pm);
+
+    /* Remember custom GSU structure assigned to
+     * u.hash->tmpdata before hash gets deleted */
+    struct gsu_scalar_ext * gsu_ext = pm->u.hash->tmpdata;
+
+    /* Uses normal unsetter. Will delete all owned
+     * parameters and also hashtable. */
+    pm->gsu.h->setfn(pm, NULL);
+
+    /* Don't need custom GSU structure with its
+     * redisContext pointer anymore */
+    zsfree(gsu_ext->redis_host_port);
+    if (gsu_ext->password)
+        zsfree(gsu_ext->password);
+    zfree(gsu_ext, sizeof(struct gsu_scalar_ext));
+
+    pm->node.flags |= PM_UNSET;
+}
+/* }}} */
+/* FUNCTION: redis_hash_untie {{{ */
+/**/
+static void
+redis_hash_untie(Param pm)
+{
+    redisContext *rc = ((struct gsu_scalar_ext *)pm->u.hash->tmpdata)->rc;
+    HashTable ht = pm->u.hash;
+
+    if (rc) { /* paranoia */
+        redisFree(rc);
+
+        /* Let hash fields know there's no backend */
+        ((struct gsu_scalar_ext *)ht->tmpdata)->rc = NULL;
+
+        /* Remove from list of tied parameters */
+        remove_tied_name(pm->node.nam);
+    }
+
+    /* for completeness ... createspecialhash() should have an inverse */
+    ht->getnode = ht->getnode2 = gethashnode2;
+    ht->scantab = NULL;
+
+    pm->node.flags &= ~(PM_SPECIAL|PM_READONLY);
+    pm->gsu.h = &stdhash_gsu;
+}
+/* }}} */
+
+/**************** STRING *****************/
+
+/* FUNCTION: redis_str_getfn {{{ */
+/**/
+static char *
+redis_str_getfn(Param pm)
+{
+    struct gsu_scalar_ext *gsu_ext;
+    char *key;
+    size_t key_len;
+    redisContext *rc;
+    redisReply *reply;
+
+    gsu_ext = (struct gsu_scalar_ext *) pm->gsu.s;
+    /* Key already retrieved? */
+    if ((pm->node.flags & PM_UPTODATE) && gsu_ext->use_cache) {
+        return pm->u.str ? pm->u.str : (char *) hcalloc(1);
+    }
+
+    key = gsu_ext->key;
+    key_len = gsu_ext->key_len;
+
+    int retry = 0;
+ retry:
+    rc = gsu_ext->rc;
+
+    reply = redisCommand(rc, "EXISTS %b", key, (size_t) key_len);
+    if (reply && reply->type == REDIS_REPLY_INTEGER && reply->integer == 1) {
+        freeReplyObject(reply);
+
+        reply = redisCommand(rc, "GET %b", key, (size_t) key_len);
+        if (reply && reply->type == REDIS_REPLY_STRING) {
+            /* We have data – store it and return it */
+            pm->node.flags |= PM_UPTODATE;
+
+            /* Ensure there's no leak */
+            if (pm->u.str) {
+                zsfree(pm->u.str);
+                pm->u.str = NULL;
+            }
+
+            /* Metafy returned data. All fits - metafy
+             * can obtain data length to avoid using \0 */
+            pm->u.str = metafy(reply->str, reply->len, META_DUP);
+            freeReplyObject(reply);
+
+            /* Can return pointer, correctly saved inside Param */
+            return pm->u.str;
+        } else if (reply) {
+            freeReplyObject(reply);
+        }
+    } else if (reply) {
+        freeReplyObject(reply);
+    }
+
+    if (!retry && (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
+        retry = 1;
+        if(reconnect(&gsu_ext->rc, gsu_ext->redis_host_port, gsu_ext->password))
+            goto retry;
+    } else if (retry && (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
+        zwarn("Aborting (no connection)");
+    }
+
+    return "";
+}
+/* }}} */
+/* FUNCTION: redis_str_setfn {{{ */
+/**/
+static void
+redis_str_setfn(Param pm, char *val)
+{
+    char *key, *content;
+    size_t key_len, content_len;
+    redisContext *rc;
+    redisReply *reply;
+
+    /* Set is done on parameter and on database. */
+
+    /* Parameter */
+    if (pm->u.str) {
+        zsfree(pm->u.str);
+        pm->u.str = NULL;
+        pm->node.flags &= ~(PM_UPTODATE);
+    }
+
+    if (val) {
+        pm->u.str = ztrdup(val);
+        pm->node.flags |= PM_UPTODATE;
+    }
+
+    /* Database */
+    struct gsu_scalar_ext *gsu_ext = (struct gsu_scalar_ext *) pm->gsu.s;
+    key = gsu_ext->key;
+    key_len = gsu_ext->key_len;
+
+    int retry = 0;
+ retry:
+    rc = gsu_ext->rc;
+
+    if (rc) {
+        if (val) {
+            /* Unmetafy with exact zalloc size */
+            int umlen = 0;
+            char *umval = unmetafy_zalloc(val, &umlen);
+
+            /* Store */
+            content = umval;
+            content_len = umlen;
+            reply = redisCommand(rc, "SET %b %b", key, (size_t) key_len, content, (size_t) content_len);
+            if (reply)
+                freeReplyObject(reply);
+
+            /* Free */
+            set_length(umval, content_len);
+            zsfree(umval);
+        } else {
+            reply = redisCommand(rc, "DEL %b", key, (size_t) key_len);
+            if (reply)
+                freeReplyObject(reply);
+        }
+
+        if (!retry && (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
+            retry = 1;
+            if(reconnect(&gsu_ext->rc, gsu_ext->redis_host_port, gsu_ext->password))
+                goto retry;
+        } else if (retry && (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
+            zwarn("Aborting (no connection)");
+        }
+    }
+}
+/* }}} */
+/* FUNCTION: redis_str_unsetfn {{{ */
+/**/
+static void
+redis_str_unsetfn(Param pm, UNUSED(int um))
+{
+    /* Will clear the database */
+    redis_str_setfn(pm, NULL);
+
+    /* Will detach from database and free custom memory */
+    redis_str_untie(pm);
+
+    pm->node.flags |= PM_UNSET;
+}
+/* }}} */
+/* FUNCTION: redis_str_untie {{{ */
+/**/
+static void
+redis_str_untie(Param pm)
+{
+    struct gsu_scalar_ext *gsu_ext = (struct gsu_scalar_ext *) pm->gsu.s;
+
+    if (gsu_ext->rc) /* paranoia */
+        redisFree(gsu_ext->rc);
+
+    /* Remove from list of tied parameters */
+    remove_tied_name(pm->node.nam);
+
+    pm->node.flags &= ~(PM_SPECIAL|PM_READONLY);
+    pm->gsu.s = &stdscalar_gsu;
+
+    /* Free gsu_ext */
+    zsfree(gsu_ext->redis_host_port);
+    if (gsu_ext->password)
+        zsfree(gsu_ext->password);
+    zsfree(gsu_ext->key);
+    zfree(gsu_ext, sizeof(struct gsu_scalar_ext));
+}
+/* }}} */
+
+/****************** SET ******************/
+
+/* FUNCTION: redis_arrset_getfn {{{ */
+
+/**/
+char **
+redis_arrset_getfn(Param pm)
+{
+    struct gsu_array_ext *gsu_ext;
+    char *key;
+    size_t key_len;
+    redisContext *rc;
+    redisReply *reply;
+    int j;
+
+    gsu_ext = (struct gsu_array_ext *) pm->gsu.a;
+    /* Key already retrieved? */
+    if ((pm->node.flags & PM_UPTODATE) && gsu_ext->use_cache) {
+        return pm->u.arr ? pm->u.arr : &my_nullarray;
+    }
+
+    key = gsu_ext->key;
+    key_len = gsu_ext->key_len;
+
+    int retry = 0;
+ retry:
+    rc = gsu_ext->rc;
+
+    if (!rc)
+        return &my_nullarray;
+
+    reply = redisCommand(rc, "EXISTS %b", key, (size_t) key_len);
+    if (reply && reply->type == REDIS_REPLY_INTEGER && reply->integer == 1) {
+        freeReplyObject(reply);
+        reply = NULL;
+
+        reply = redisCommand(rc, "SMEMBERS %b", key, (size_t) key_len);
+        if (reply && reply->type == REDIS_REPLY_ARRAY) {
+            /* We have data – store it and return it */
+            pm->node.flags |= PM_UPTODATE;
+
+            /* Ensure there's no leak */
+            if (pm->u.arr) {
+                freearray(pm->u.arr);
+                pm->u.arr = NULL;
+            }
+
+            pm->u.arr = zalloc((reply->elements + 1) * sizeof(char*));
+
+            for (j = 0; j < reply->elements; j++) {
+                if (NULL == reply->element[j]) {
+                    pm->u.arr[j] = ztrdup("");
+                    zwarn("Error 10 when fetching set elements");
+                    continue;
+                } else if (reply->element[j]->type != REDIS_REPLY_STRING) {
+                    pm->u.arr[j] = ztrdup("");
+                    if (NULL != reply->element[j]->str && reply->element[j]->len > 0) {
+                        zwarn("Error 11 when fetching set elements (message: %s)", reply->element[j]->str);
+                    } else {
+                        zwarn("Error 11 when fetching set elements");
+                    }
+                    continue;
+                }
+                /* Metafy returned data. All fits - metafy
+                 * can obtain data length to avoid using \0 */
+                pm->u.arr[j] = metafy(reply->element[j]->str,
+                                      reply->element[j]->len,
+                                      META_DUP);
+            }
+            pm->u.arr[reply->elements] = NULL;
+
+            freeReplyObject(reply);
+            reply = NULL;
+
+            /* Can return pointer, correctly saved inside Param */
+            return pm->u.arr;
+        } else if (reply) {
+            freeReplyObject(reply);
+            reply = NULL;
+        }
+    } else if (reply) {
+        freeReplyObject(reply);
+        reply = NULL;
+    }
+
+    /* Disconnect detection */
+    if (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF)) {
+        if (reply) {
+            freeReplyObject(reply);
+            reply = NULL;
+        }
+        if (retry) {
+            zwarn("Aborting (no connection)");
+            return &my_nullarray;
+        }
+        retry = 1;
+        if(reconnect(&gsu_ext->rc, gsu_ext->redis_host_port, gsu_ext->password))
+            goto retry;
+    }
+
+    /* Array with 0 elements */
+    return &my_nullarray;
+}
+/* }}} */
+/* FUNCTION: redis_arrset_setfn {{{ */
+/**/
+mod_export void
+redis_arrset_setfn(Param pm, char **val)
+{
+    char *key, *content;
+    size_t key_len, content_len;
+    int alen = 0, j;
+    redisContext *rc;
+    redisReply *reply;
+
+    /* Set is done on parameter and on database. */
+
+    /* Parameter */
+    if (pm->u.arr && pm->u.arr != val) {
+        freearray(pm->u.arr);
+        pm->u.arr = NULL;
+        pm->node.flags &= ~(PM_UPTODATE);
+    }
+
+    if (val) {
+        uniqarray(val);
+        alen = arrlen(val);
+        pm->u.arr = val;
+        pm->node.flags |= PM_UPTODATE;
+    }
+
+    /* Database */
+    struct gsu_array_ext *gsu_ext = (struct gsu_array_ext *) pm->gsu.a;
+    rc = gsu_ext->rc;
+    key = gsu_ext->key;
+    key_len = gsu_ext->key_len;
+
+    if (rc) {
+
+        int retry = 0;
+    retry:
+        rc = gsu_ext->rc;
+
+        if (!rc)
+            return;
+
+        reply = redisCommand(rc, "DEL %b", key, (size_t) key_len);
+        if (reply) {
+            freeReplyObject(reply);
+            reply = NULL;
+        }
+
+        /* Disconnect detection */
+        if (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF)) {
+            if (retry) {
+                zwarn("Aborting (no connection)");
+                return;
+            }
+            retry = 1;
+            if(reconnect(&gsu_ext->rc, gsu_ext->redis_host_port, gsu_ext->password))
+                goto retry;
+            else
+                return;
+        }
+
+        if (val) {
+            for (j=0; j<alen; j ++) {
+                /* Unmetafy with exact zalloc size */
+                int umlen = 0;
+                char *umval = unmetafy_zalloc(val[j], &umlen);
+
+                /* Store */
+                content = umval;
+                content_len = umlen;
+                reply = redisCommand(rc, "SADD %b %b", key, (size_t) key_len, content, (size_t) content_len);
+                if (reply) {
+                    freeReplyObject(reply);
+                    reply = NULL;
+                }
+                if (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF)) {
+                    break;
+                }
+
+                /* Free */
+                set_length(umval, umlen);
+                zsfree(umval);
+            }
+
+            /* Disconnect detection */
+            if (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF)) {
+                if (retry) {
+                    zwarn("Aborting (no connection)");
+                    return;
+                }
+                retry = 1;
+                if(reconnect(&gsu_ext->rc, gsu_ext->redis_host_port, gsu_ext->password))
+                    goto retry;
+            }
+        }
+    }
+
+    if (pm->ename && val)
+        arrfixenv(pm->ename, val);
+}
+/* }}} */
+/* FUNCTION: redis_arrset_unsetfn {{{ */
+/**/
+mod_export void
+redis_arrset_unsetfn(Param pm, UNUSED(int exp))
+{
+    /* Will clear the database */
+    redis_arrset_setfn(pm, NULL);
+
+    /* Will detach from database and free custom memory */
+    redis_arrset_untie(pm);
+
+    pm->node.flags |= PM_UNSET;
+}
+/* }}} */
+/* FUNCTION: redis_arrset_untie {{{ */
+/**/
+static void
+redis_arrset_untie(Param pm)
+{
+    struct gsu_array_ext *gsu_ext = (struct gsu_array_ext *) pm->gsu.a;
+
+    if (gsu_ext->rc) /* paranoia */
+        redisFree(gsu_ext->rc);
+
+    /* Remove from list of tied parameters */
+    remove_tied_name(pm->node.nam);
+
+    pm->node.flags &= ~(PM_SPECIAL|PM_READONLY);
+    pm->gsu.a = &stdarray_gsu;
+
+    /* Free gsu_ext */
+    zsfree(gsu_ext->redis_host_port);
+    if (gsu_ext->password)
+        zsfree(gsu_ext->password);
+    zsfree(gsu_ext->key);
+    zfree(gsu_ext, sizeof(struct gsu_array_ext));
+}
+/* }}} */
+
+/************ ZSET HASH ELEM *************/
+
+/* FUNCTION: redis_zset_getfn {{{ */
+
+/*
+ * The param is actual param in hash – always, because
+ * redis_zset_get_node creates every new key seen. However,
+ * it might be not PM_UPTODATE - which means that database
+ * wasn't yet queried.
+ *
+ * It will be left in this state if database doesn't
+ * contain such key. That might be a drawback, maybe
+ * setting to empty value has sense.
+ */
+
+/**/
+static char *
+redis_zset_getfn(Param pm)
+{
+    struct gsu_scalar_ext *gsu_ext;
+    char *main_key, *key;
+    size_t main_key_len, key_len;
+    redisContext *rc;
+    redisReply *reply;
+
+    gsu_ext = (struct gsu_scalar_ext *) pm->gsu.s;
+
+    /* Key already retrieved? */
+    if ((pm->node.flags & PM_UPTODATE) && gsu_ext->use_cache) {
+        return pm->u.str ? pm->u.str : (char *) hcalloc(1);
+    }
+
+    /* Unmetafy key. Redis fits nice into this
+     * process, as it can use length of data */
+    int umlen = 0;
+    char *umkey = unmetafy_zalloc(pm->node.nam, &umlen);
+
+    key = umkey;
+    key_len = umlen;
+
+    main_key = gsu_ext->key;
+    main_key_len = gsu_ext->key_len;
+
+    int retry = 0;
+ retry:
+    rc = gsu_ext->rc;
+
+    if(rc) {
+        reply = redisCommand(rc, "ZSCORE %b %b", main_key, (size_t) main_key_len, key, (size_t) key_len);
+        if (reply && reply->type == REDIS_REPLY_STRING) {
+            /* We have data – store it and return it */
+            pm->node.flags |= PM_UPTODATE;
+
+            /* Ensure there's no leak */
+            if (pm->u.str) {
+                zsfree(pm->u.str);
+                pm->u.str = NULL;
+            }
+
+            /* Metafy returned data. All fits - metafy
+             * can obtain data length to avoid using \0 */
+            pm->u.str = metafy(reply->str, reply->len, META_DUP);
+            freeReplyObject(reply);
+
+            /* Free key, restoring its original length */
+            set_length(umkey, key_len);
+            zsfree(umkey);
+
+            /* Can return pointer, correctly saved inside hash */
+            return pm->u.str;
+        } else if (reply) {
+            freeReplyObject(reply);
+        }
+
+        if (!retry && (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
+            retry = 1;
+            if(reconnect(&gsu_ext->rc, gsu_ext->redis_host_port, gsu_ext->password))
+                goto retry;
+        } else if (retry && (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
+            zwarn("Aborting (no connection)");
+        }
+    }
+
+    /* Free key, restoring its original length */
+    set_length(umkey, key_len);
+    zsfree(umkey);
+
+    return "";
+}
+/* }}} */
+/* FUNCTION: redis_zset_setfn {{{ */
+/**/
+static void
+redis_zset_setfn(Param pm, char *val)
+{
+    char *main_key, *key, *content;
+    size_t main_key_len, key_len, content_len;
+    struct gsu_scalar_ext *gsu_ext;
+    redisContext *rc;
+    redisReply *reply;
+
+    /* Set is done on parameter and on database. */
+
+    /* Parameter */
+    if (pm->u.str) {
+        zsfree(pm->u.str);
+        pm->u.str = NULL;
+        pm->node.flags &= ~(PM_UPTODATE);
+    }
+
+    if (val) {
+        pm->u.str = ztrdup(val);
+        pm->node.flags |= PM_UPTODATE;
+    }
+
+    /* Database */
+    gsu_ext = (struct gsu_scalar_ext *) pm->gsu.s;
+
+    int retry = 0;
+ retry:
+    rc = gsu_ext->rc;
+
+    /* Can be NULL, when calling unset after untie */
+    if (rc && no_database_action == 0) {
+        int umlen = 0;
+        char *umkey = unmetafy_zalloc(pm->node.nam, &umlen);
+
+        key = umkey;
+        key_len = umlen;
+
+        main_key = gsu_ext->key;
+        main_key_len = gsu_ext->key_len;
+
+        if (val) {
+            /* Unmetafy with exact zalloc size */
+            char *umval = unmetafy_zalloc(val, &umlen);
+
+            content = umval;
+            content_len = umlen;
+
+            /* ZADD myzset 1.0 element1 */
+            reply = redisCommand(rc, "ZADD %b %b %b",
+                                 main_key, (size_t) main_key_len,
+                                 content, (size_t) content_len,
+                                 key, (size_t) key_len );
+            if (reply)
+                freeReplyObject(reply);
+
+            /* Free */
+            set_length(umval, content_len);
+            zsfree(umval);
+        } else {
+            reply = redisCommand(rc, "ZREM %b %b", main_key, (size_t) main_key_len, key, (size_t) key_len);
+            if (reply)
+                freeReplyObject(reply);
+        }
+
+        /* Free key */
+        set_length(umkey, key_len);
+        zsfree(umkey);
+
+        if (!retry && (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
+            retry = 1;
+            if(reconnect(&gsu_ext->rc, gsu_ext->redis_host_port, gsu_ext->password))
+                goto retry;
+        } else if (retry && (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
+            zwarn("Aborting (no connection)");
+        }
+    }
+}
+/* }}} */
+/* FUNCTION: redis_zset_unsetfn {{{ */
+/**/
+static void
+redis_zset_unsetfn(Param pm, UNUSED(int um))
+{
+    /* Set with NULL */
+    redis_zset_setfn(pm, NULL);
+}
+/* }}} */
+
+/*************** ZSET HASH ***************/
+
+/* FUNCTION: redis_zset_get_node {{{ */
+/**/
+static HashNode
+redis_zset_get_node(HashTable ht, const char *name)
+{
+    HashNode hn = gethashnode2(ht, name);
+    Param val_pm = (Param) hn;
+
+    /* Entry for key doesn't exist? Create it now,
+     * it will be interfacing between the database
+     * and Zsh - through special gsu. So, any seen
+     * key results in new interfacing parameter.
+     *
+     * Add the Param to its hash, it is not PM_UPTODATE.
+     * It will be loaded from database *and filled*
+     * or left in that state if the database doesn't
+     * contain it.
+     */
+
+    if (!val_pm) {
+        val_pm = (Param) zshcalloc(sizeof (*val_pm));
+        val_pm->node.flags = PM_SCALAR | PM_HASHELEM; /* no PM_UPTODATE */
+        val_pm->gsu.s = (GsuScalar) ht->tmpdata;
+        ht->addnode(ht, ztrdup(name), val_pm); // sets pm->node.nam
+    }
+
+    return (HashNode) val_pm;
+}
+/* }}} */
+/* FUNCTION: zset_scan_keys {{{ */
+/**/
+static void
+zset_scan_keys(HashTable ht, ScanFunc func, int flags)
+{
+    char *main_key, *key;
+    size_t main_key_len, key_len;
+    unsigned long long cursor = 0;
+    redisContext *rc;
+    redisReply *reply, *reply2;
+    struct gsu_scalar_ext *gsu_ext;
+
+    gsu_ext = (struct gsu_scalar_ext *) ht->tmpdata;
+    main_key = gsu_ext->key;
+    main_key_len = gsu_ext->key_len;
+
+    /* Iterate keys adding them to hash, so we have Param to use in `func` */
+    do {
+        int retry = 0;
+    retry:
+        rc = gsu_ext->rc;
+
+        if (!rc)
+            return;
+
+        reply = redisCommand(rc, "ZSCAN %b %llu", main_key, (size_t) main_key_len, cursor);
+
+        /* Disconnect detection */
+        if (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF)) {
+            if (reply)
+                freeReplyObject(reply);
+            if (retry) {
+                zwarn("Aborting (not connected)");
+                break;
+            }
+            retry = 1;
+            if(reconnect(&gsu_ext->rc, gsu_ext->redis_host_port, gsu_ext->password))
+                goto retry;
+            else
+                break;
+        }
+
+        if (reply == NULL || reply->type != REDIS_REPLY_ARRAY || reply->elements != 2) {
+            if (reply && reply->type == REDIS_REPLY_ERROR) {
+                zwarn("Aborting, problem occured during ZSCAN: %s", reply->str);
+            } else {
+                zwarn("Problem occured during ZSCAN, no error message available, aborting");
+            }
+            if (reply)
+                freeReplyObject(reply);
+            break;
+        }
+
+        /* Get new cursor */
+        if (reply->element[0]->type == REDIS_REPLY_STRING) {
+            cursor = strtoull(reply->element[0]->str, NULL, 10);
+        } else {
+            zwarn("Error 2 occured during ZSCAN");
+            break;
+        }
+
+        reply2 = reply->element[1];
+        if (reply2 == NULL || reply2->type != REDIS_REPLY_ARRAY) {
+            zwarn("Error 3 occured during ZSCAN");
+            break;
+        }
+
+        for (size_t j = 0; j < reply2->elements; j+= 2) {
+            redisReply *entry = reply2->element[j];
+            if (entry == NULL || entry->type != REDIS_REPLY_STRING) {
+                continue;
+            }
+
+            key = entry->str;
+            key_len = entry->len;
+
+            /* This returns database-interfacing Param,
+            * it will return u.str or first fetch data
+            * if not PM_UPTODATE (newly created) */
+            char *zkey = metafy(key, key_len, META_DUP);
+            HashNode hn = redis_zset_get_node(ht, zkey);
+            zsfree(zkey);
+
+            func(hn, flags);
+        }
+        freeReplyObject(reply);
+    } while (cursor != 0);
+}
+/* }}} */
+/* FUNCTION: redis_hash_zset_setfn {{{ */
+
+/*
+ * Replace database with new hash
+ */
+
+/**/
+static void
+redis_hash_zset_setfn(Param pm, HashTable ht)
+{
+    HashNode hn;
+    char *main_key, *key, *content;
+    size_t main_key_len, key_len, content_len, i;
+    redisContext *rc;
+    redisReply *reply;
+    struct gsu_scalar_ext *gsu_ext;
+
+    if (!pm->u.hash || pm->u.hash == ht)
+        return;
+
+    gsu_ext = (struct gsu_scalar_ext *) pm->u.hash->tmpdata;
+
+    int retry = 0;
+ retry:
+    rc = gsu_ext->rc;
+    if (!rc) {
+        return;
+    }
+
+    main_key = gsu_ext->key;
+    main_key_len = gsu_ext->key_len;
+
+    /* PRUNE */
+    reply = redisCommand(rc, "ZREMRANGEBYSCORE %b -inf +inf", main_key, (size_t) main_key_len);
+    if (reply == NULL || reply->type != REDIS_REPLY_INTEGER) {
+        zwarn("Error 4 occured, database not updated (no purge of zset)");
+        if (reply)
+            freeReplyObject(reply);
+        goto do_retry;
+    }
+    freeReplyObject(reply);
+
+ do_retry:
+    if (!retry && (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
+        retry = 1;
+        if(reconnect(&gsu_ext->rc, gsu_ext->redis_host_port, gsu_ext->password))
+            goto retry;
+        else
+            return;
+    } else if (retry && (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
+        zwarn("Aborting (no connection)");
+        return;
+    }
+
+    no_database_action = 1;
+    emptyhashtable(pm->u.hash);
+    no_database_action = 0;
+
+    if (!ht)
+        return;
+
+     /* Put new strings into database, having
+      * their interfacing-Params created */
+
+    retry = 0;
+ retry2:
+    rc = gsu_ext->rc;
+
+    for (i = 0; i < ht->hsize; i++) {
+        for (hn = ht->nodes[i]; hn; hn = hn->next) {
+            struct value v;
+
+            v.isarr = v.flags = v.start = 0;
+            v.end = -1;
+            v.arr = NULL;
+            v.pm = (Param) hn;
+
+            /* Unmetafy key */
+            int umlen = 0;
+            char *umkey = unmetafy_zalloc(v.pm->node.nam, &umlen);
+
+            key = umkey;
+            key_len = umlen;
+
+            queue_signals();
+
+            /* Unmetafy data */
+            char *umval = unmetafy_zalloc(getstrvalue(&v), &umlen);
+
+            content = umval;
+            content_len = umlen;
+
+            /* ZADD myzset 1.0 element1 */
+            reply = redisCommand(rc, "ZADD %b %b %b", main_key, (size_t) main_key_len,
+                                 content, (size_t) content_len,
+                                 key, (size_t) key_len);
+            if (reply) {
+                freeReplyObject(reply);
+                reply = NULL;
+            }
+            if (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF))
+                break;
+
+            /* Free, restoring original length */
+            set_length(umval, content_len);
+            zsfree(umval);
+            set_length(umkey, key_len);
+            zsfree(umkey);
+
+            unqueue_signals();
+        }
+
+        /* Disconnect detection */
+        if (!retry && (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
+            retry = 1;
+            if(reconnect(&gsu_ext->rc, gsu_ext->redis_host_port, gsu_ext->password))
+                goto retry2;
+            else
+                return;
+        } else if (retry && (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
+            zwarn("Aborting (no connection)");
+            return;
+        }
+    }
+}
+/* }}} */
+/* FUNCTION: redis_hash_zset_unsetfn {{{ */
+/**/
+static void
+redis_hash_zset_unsetfn(Param pm, UNUSED(int exp))
+{
+    /* This will make database contents survive the
+     * unset, as standard GSU will be put in place */
+    redis_hash_zset_untie(pm);
+
+    /* Remember custom GSU structure assigned to
+     * u.hash->tmpdata before hash gets deleted */
+    struct gsu_scalar_ext * gsu_ext = pm->u.hash->tmpdata;
+
+    /* Uses normal unsetter. Will delete all owned
+     * parameters and also hashtable. */
+    pm->gsu.h->setfn(pm, NULL);
+
+    /* Don't need custom GSU structure with its
+     * redisContext pointer anymore */
+    zsfree(gsu_ext->redis_host_port);
+    if (gsu_ext->password)
+        zsfree(gsu_ext->password);
+    zsfree(gsu_ext->key);
+    zfree(gsu_ext, sizeof(struct gsu_scalar_ext));
+
+    pm->node.flags |= PM_UNSET;
+}
+/* }}} */
+/* FUNCTION: redis_hash_zset_untie {{{ */
+/**/
+static void
+redis_hash_zset_untie(Param pm)
+{
+    redisContext *rc = ((struct gsu_scalar_ext *)pm->u.hash->tmpdata)->rc;
+    HashTable ht = pm->u.hash;
+
+    if (rc) { /* paranoia */
+        redisFree(rc);
+
+        /* Let hash fields know there's no backend */
+        ((struct gsu_scalar_ext *)ht->tmpdata)->rc = NULL;
+
+        /* Remove from list of tied parameters */
+        remove_tied_name(pm->node.nam);
+    }
+
+    /* for completeness ... createspecialhash() should have an inverse */
+    ht->getnode = ht->getnode2 = gethashnode2;
+    ht->scantab = NULL;
+
+    pm->node.flags &= ~(PM_SPECIAL|PM_READONLY);
+    pm->gsu.h = &stdhash_gsu;
+}
+/* }}} */
+
+/*************** ZSET UTIL ***************/
+
+/* FUNCTION: bin_zrzset {{{ */
+/**/
+static int
+bin_zrzset(char *nam, char **args, Options ops, UNUSED(int func))
+{
+    Param pm;
+    int i;
+    const char *pmname;
+
+    if (OPT_ISSET(ops,'h')) {
+        zrzset_usage();
+        return 0;
+    }
+
+    pmname = *args;
+
+    if (!pmname) {
+        zwarnnam(nam, "zset parameter name (to be copied to $reply) is required");
+        return 1;
+    }
+
+    pm = (Param) paramtab->getnode(paramtab, pmname);
+    if(!pm) {
+        zwarnnam(nam, "no such parameter: %s", pmname);
+        return 1;
+    }
+
+    if (pm->gsu.h == &redis_hash_gsu) {
+        zwarnnam(nam, "`%s' is a main-storage hash, aborting", pmname);
+    } else if(pm->gsu.s->getfn == &redis_str_getfn) {
+        zwarnnam(nam, "`%s' is a string parameter, aborting", pmname);
+    } else if(pm->gsu.a->getfn == &redis_arrset_getfn) {
+        zwarnnam(nam, "`%s' is a set (array) parameter, aborting", pmname);
+    } else if(pm->gsu.h == &hash_zset_gsu) {
+        char **arr, *main_key;
+        size_t main_key_len;
+        redisContext *rc;
+        redisReply *reply, *entry;
+        struct gsu_scalar_ext *gsu_ext;
+
+        gsu_ext = (struct gsu_scalar_ext *) pm->u.hash->tmpdata;
+        rc = gsu_ext->rc;
+        main_key = gsu_ext->key;
+        main_key_len = gsu_ext->key_len;
+
+        if (rc) {
+            reply = redisCommand(rc, "ZRANGE %b 0 -1", main_key, (size_t) main_key_len);
+            if (reply == NULL || reply->type != REDIS_REPLY_ARRAY) {
+                zwarn("Error 12 occured (ZRANGE call), aborting");
+                if (reply)
+                    freeReplyObject(reply);
+                return 1;
+            }
+
+            arr = zshcalloc((reply->elements+1) * sizeof(char *));
+            for (i = 0; i < reply->elements; i++) {
+                entry = reply->element[i];
+                arr[i] = metafy(entry->str, entry->len, META_DUP);
+            }
+            arr[reply->elements] = NULL;
+            freeReplyObject(reply);
+
+            pm = assignaparam("reply", arr, 0);
+            return 0;
+        } else {
+            zwarn("Error 13 occured: no database connection");
+            return 1;
+        }
+    } else if(pm->gsu.h == &hash_hset_gsu) {
+        zwarnnam(nam, "`%s' is a hset (hash) parameter, aborting", pmname);
+    } else if(pm->gsu.a->getfn == &redis_arrlist_getfn) {
+        zwarnnam(nam, "`%s' is a list (array) parameter, aborting", pmname);
+    } else {
+        zwarnnam(nam, "not a tied zredis parameter: `%s', $reply array unchanged", pmname);
+    }
+
+    return 1;
+}
+/* }}} */
+
+/************ HSET HASH ELEM *************/
+
+/* FUNCTION: redis_hset_getfn {{{ */
+
+/*
+ * The param is actual param in hash – always, because
+ * redis_hset_get_node creates every new key seen. However,
+ * it might be not PM_UPTODATE - which means that database
+ * wasn't yet queried.
+ *
+ * It will be left in this state if database doesn't
+ * contain such key. That might be a drawback, maybe
+ * setting to empty value has sense.
+ */
+
+/**/
+static char *
+redis_hset_getfn(Param pm)
+{
+    struct gsu_scalar_ext *gsu_ext;
+    char *main_key, *key;
+    size_t main_key_len, key_len;
+    redisContext *rc;
+    redisReply *reply;
+
+    gsu_ext = (struct gsu_scalar_ext *) pm->gsu.s;
+
+    /* Key already retrieved? */
+    if ((pm->node.flags & PM_UPTODATE) && gsu_ext->use_cache) {
+        return pm->u.str ? pm->u.str : (char *) hcalloc(1);
+    }
+
+    /* Unmetafy key. Redis fits nice into this
+     * process, as it can use length of data */
+    int umlen = 0;
+    char *umkey = unmetafy_zalloc(pm->node.nam, &umlen);
+
+    key = umkey;
+    key_len = umlen;
+
+    main_key = gsu_ext->key;
+    main_key_len = gsu_ext->key_len;
+
+    int retry = 0;
+ retry:
+    rc = gsu_ext->rc;
+
+    if (rc) {
+        reply = redisCommand(rc, "HGET %b %b", main_key, (size_t) main_key_len, key, (size_t) key_len);
+        if (reply && reply->type == REDIS_REPLY_STRING) {
+            /* We have data – store it and return it */
+            pm->node.flags |= PM_UPTODATE;
+
+            /* Ensure there's no leak */
+            if (pm->u.str) {
+                zsfree(pm->u.str);
+                pm->u.str = NULL;
+            }
+
+            /* Metafy returned data. All fits - metafy
+             * can obtain data length to avoid using \0 */
+            pm->u.str = metafy(reply->str, reply->len, META_DUP);
+            freeReplyObject(reply);
+
+            /* Free key, restoring its original length */
+            set_length(umkey, key_len);
+            zsfree(umkey);
+
+            /* Can return pointer, correctly saved inside hash */
+            return pm->u.str;
+        } else if (reply) {
+            freeReplyObject(reply);
+        }
+
+        if (!retry && (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
+            retry = 1;
+            if(reconnect(&gsu_ext->rc, gsu_ext->redis_host_port, gsu_ext->password))
+                goto retry;
+        } else if (retry && (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
+            zwarn("Aborting (no connection)");
+        }
+    }
+
+    /* Free key, restoring its original length */
+    set_length(umkey, key_len);
+    zsfree(umkey);
+
+    /* Can this be "" ? */
+    return (char *) hcalloc(1);
+}
+/* }}} */
+/* FUNCTION: redis_hset_setfn {{{ */
+/**/
+static void
+redis_hset_setfn(Param pm, char *val)
+{
+    char *main_key, *key, *content;
+    size_t main_key_len, key_len, content_len;
+    struct gsu_scalar_ext *gsu_ext;
+    redisContext *rc;
+    redisReply *reply;
+
+    /* Set is done on parameter and on database. */
+
+    /* Parameter */
+    if (pm->u.str) {
+        zsfree(pm->u.str);
+        pm->u.str = NULL;
+        pm->node.flags &= ~(PM_UPTODATE);
+    }
+
+    if (val) {
+        pm->u.str = ztrdup(val);
+        pm->node.flags |= PM_UPTODATE;
+    }
+
+    /* Database */
+    gsu_ext = (struct gsu_scalar_ext *) pm->gsu.s;
+
+    int retry = 0;
+ retry:
+    rc = gsu_ext->rc;
+
+    /* Can be NULL, when calling unset after untie */
+    if (rc && no_database_action == 0) {
+        int umlen = 0;
+        char *umkey = unmetafy_zalloc(pm->node.nam, &umlen);
+
+        key = umkey;
+        key_len = umlen;
+
+        main_key = gsu_ext->key;
+        main_key_len = gsu_ext->key_len;
+
+        if (val) {
+            /* Unmetafy with exact zalloc size */
+            char *umval = unmetafy_zalloc(val, &umlen);
+
+            content = umval;
+            content_len = umlen;
+
+            /* HSET myhset field1 value */
+            reply = redisCommand(rc, "HSET %b %b %b",
+                                 main_key, (size_t) main_key_len,
+                                 key, (size_t) key_len,
+                                 content, (size_t) content_len);
+            if (reply)
+                freeReplyObject(reply);
+
+            /* Free */
+            set_length(umval, content_len);
+            zsfree(umval);
+        } else {
+            reply = redisCommand(rc, "HDEL %b %b", main_key, (size_t) main_key_len, key, (size_t) key_len);
+            if (reply)
+                freeReplyObject(reply);
+        }
+
+        /* Free key */
+        set_length(umkey, key_len);
+        zsfree(umkey);
+
+        if (!retry && (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
+            retry = 1;
+            if(reconnect(&gsu_ext->rc, gsu_ext->redis_host_port, gsu_ext->password))
+                goto retry;
+        } else if (retry && (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
+            zwarn("Aborting (no connection)");
+        }
+    }
+}
+/* }}} */
+/* FUNCTION: redis_hset_unsetfn {{{ */
+/**/
+static void
+redis_hset_unsetfn(Param pm, UNUSED(int um))
+{
+    /* Set with NULL */
+    redis_hset_setfn(pm, NULL);
+}
+/* }}} */
+
+/*************** HSET HASH ***************/
+
+/* FUNCTION: redis_hset_get_node {{{ */
+/**/
+static HashNode
+redis_hset_get_node(HashTable ht, const char *name)
+{
+    HashNode hn = gethashnode2(ht, name);
+    Param val_pm = (Param) hn;
+
+    /* Entry for key doesn't exist? Create it now,
+     * it will be interfacing between the database
+     * and Zsh - through special gsu. So, any seen
+     * key results in new interfacing parameter.
+     *
+     * Add the Param to its hash, it is not PM_UPTODATE.
+     * It will be loaded from database *and filled*
+     * or left in that state if the database doesn't
+     * contain it.
+     */
+
+    if (!val_pm) {
+        val_pm = (Param) zshcalloc(sizeof (*val_pm));
+        val_pm->node.flags = PM_SCALAR | PM_HASHELEM; /* no PM_UPTODATE */
+        val_pm->gsu.s = (GsuScalar) ht->tmpdata;
+        ht->addnode(ht, ztrdup(name), val_pm); // sets pm->node.nam
+    }
+
+    return (HashNode) val_pm;
+}
+/* }}} */
+/* FUNCTION: hset_scan_keys {{{ */
+/**/
+static void
+hset_scan_keys(HashTable ht, ScanFunc func, int flags)
+{
+    char *main_key, *key;
+    size_t main_key_len, key_len;
+    unsigned long long cursor = 0;
+    redisContext *rc;
+    redisReply *reply, *reply2;
+    struct gsu_scalar_ext *gsu_ext;
+
+    gsu_ext = (struct gsu_scalar_ext *) ht->tmpdata;
+    main_key = gsu_ext->key;
+    main_key_len = gsu_ext->key_len;
+
+    /* Iterate keys adding them to hash, so we have Param to use in `func` */
+    do {
+        int retry = 0;
+    retry:
+        rc = gsu_ext->rc;
+
+        if (!rc)
+            return;
+
+        reply = redisCommand(rc, "HSCAN %b %llu", main_key, (size_t) main_key_len, cursor);
+
+
+        /* Disconnect detection */
+        if (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF)) {
+            if (reply)
+                freeReplyObject(reply);
+            if (retry) {
+                zwarn("Aborting (not connected)");
+                break;
+            }
+            retry = 1;
+            if(reconnect(&gsu_ext->rc, gsu_ext->redis_host_port, gsu_ext->password))
+                // The same cursor
+                goto retry;
+            else
+                break;
+        }
+
+        if (reply == NULL || reply->type != REDIS_REPLY_ARRAY || reply->elements != 2) {
+            if (reply && reply->type == REDIS_REPLY_ERROR) {
+                zwarn("Aborting, problem occured during HSCAN: %s", reply->str);
+            } else {
+                zwarn("Problem occured during HSCAN, no error message available, aborting");
+            }
+            if (reply)
+                freeReplyObject(reply);
+            break;
+        }
+
+        /* Get new cursor */
+        if (reply->element[0]->type == REDIS_REPLY_STRING) {
+            cursor = strtoull(reply->element[0]->str, NULL, 10);
+        } else {
+            zwarn("Error 2 occured during HSCAN");
+            break;
+        }
+
+        reply2 = reply->element[1];
+        if (reply2 == NULL || reply2->type != REDIS_REPLY_ARRAY) {
+            zwarn("Error 3 occured during HSCAN");
+            break;
+        }
+
+        for (size_t j = 0; j < reply2->elements; j+= 2) {
+            redisReply *entry = reply2->element[j];
+            if (entry == NULL || entry->type != REDIS_REPLY_STRING) {
+                continue;
+            }
+
+            key = entry->str;
+            key_len = entry->len;
+
+            /* This returns database-interfacing Param,
+             * it will return u.str or first fetch data
+             * if not PM_UPTODATE (newly created) */
+            char *zkey = metafy(key, key_len, META_DUP);
+            HashNode hn = redis_hset_get_node(ht, zkey);
+            zsfree(zkey);
+
+            func(hn, flags);
+        }
+        freeReplyObject(reply);
+    } while (cursor != 0);
+}
+/* }}} */
+/* FUNCTION: redis_hash_hset_setfn {{{ */
+
+/*
+ * Replace database with new hash
+ */
+
+/**/
+static void
+redis_hash_hset_setfn(Param pm, HashTable ht)
+{
+    HashNode hn;
+    char *main_key, *key, *content;
+    size_t main_key_len, key_len, content_len, i;
+    redisContext *rc;
+    redisReply *reply, *reply2, *entry;
+    struct gsu_scalar_ext *gsu_ext;
+
+    if (!pm->u.hash || pm->u.hash == ht)
+        return;
+
+    gsu_ext = (struct gsu_scalar_ext *) pm->u.hash->tmpdata;
+
+    int retry = 0;
+ retry:
+    rc = gsu_ext->rc;
+    if (!rc) {
+        return;
+    }
+
+    main_key = gsu_ext->key;
+    main_key_len = gsu_ext->key_len;
+
+    /* PRUNE */
+    reply = redisCommand(rc, "HKEYS %b", main_key, (size_t) main_key_len);
+    if (reply == NULL || reply->type != REDIS_REPLY_ARRAY) {
+        zwarn("Error 5 occured (redis communication), database and tied hash not updated");
+        if (reply) {
+            freeReplyObject(reply);
+            reply = NULL;
+        }
+        goto do_retry;
+    }
+
+    for (i = 0; i < reply->elements; i++) {
+        entry = reply->element[i];
+        if (NULL == entry || entry->type != REDIS_REPLY_STRING) {
+            if (entry && entry->str && entry->len > 0) {
+                zwarn("Error 6 when replacing database (element %d, message: %s)", i+1, reply2->str);
+            } else {
+                zwarn("Error 6 when replacing database (element %d)", i+1);
+            }
+            continue;
+        }
+
+        /* HDEL myhset myfield */
+        reply2 = redisCommand(rc, "HDEL %b %b", main_key, (size_t) main_key_len, entry->str, (size_t) entry->len);
+        if (NULL == reply2 || reply2->type != REDIS_REPLY_INTEGER) {
+            zwarn("Error 7 when replacing database");
+            continue;
+        }
+        freeReplyObject(reply2);
+        reply2 = NULL;
+        if (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF))
+            break;
+    }
+
+ do_retry:
+
+    if (reply) {
+        freeReplyObject(reply);
+        reply = NULL;
+    }
+
+    if (!retry && (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
+        retry = 1;
+        if(reconnect(&gsu_ext->rc, gsu_ext->redis_host_port, gsu_ext->password))
+            goto retry;
+        else
+            return;
+    } else if (retry && (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
+        zwarn("Aborting (no connection)");
+        return;
+    }
+
+    no_database_action = 1;
+    emptyhashtable(pm->u.hash);
+    no_database_action = 0;
+
+    if (!ht)
+        return;
+
+     /* Put new strings into database, having
+      * their interfacing-Params created */
+
+    retry = 0;
+ retry2:
+    rc = gsu_ext->rc;
+
+    for (i = 0; i < ht->hsize; i++) {
+        for (hn = ht->nodes[i]; hn; hn = hn->next) {
+            struct value v;
+
+            v.isarr = v.flags = v.start = 0;
+            v.end = -1;
+            v.arr = NULL;
+            v.pm = (Param) hn;
+
+            /* Unmetafy key */
+            int umlen = 0;
+            char *umkey = unmetafy_zalloc(v.pm->node.nam, &umlen);
+
+            key = umkey;
+            key_len = umlen;
+
+            queue_signals();
+
+            /* Unmetafy data */
+            char *umval = unmetafy_zalloc(getstrvalue(&v), &umlen);
+
+            content = umval;
+            content_len = umlen;
+
+            /* HSET myhset element1 value */
+            reply = redisCommand(rc, "HSET %b %b %b", main_key, (size_t) main_key_len,
+                                 key, (size_t) key_len,
+                                 content, (size_t) content_len);
+            if (reply)
+                freeReplyObject(reply);
+
+            /* Free, restoring original length */
+            set_length(umval, content_len);
+            zsfree(umval);
+            set_length(umkey, key_len);
+            zsfree(umkey);
+
+            unqueue_signals();
+        }
+
+        /* Disconnect detection */
+        if (!retry && (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
+            retry = 1;
+            if(reconnect(&gsu_ext->rc, gsu_ext->redis_host_port, gsu_ext->password))
+                goto retry2;
+            else
+                return;
+        } else if (retry && (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
+            zwarn("Aborting (no connection)");
+            return;
+        }
+    }
+}
+/* }}} */
+/* FUNCTION: redis_hash_hset_unsetfn {{{ */
+/**/
+static void
+redis_hash_hset_unsetfn(Param pm, UNUSED(int exp))
+{
+    /* This will make database contents survive the
+     * unset, as standard GSU will be put in place */
+    redis_hash_hset_untie(pm);
+
+    /* Remember custom GSU structure assigned to
+     * u.hash->tmpdata before hash gets deleted */
+    struct gsu_scalar_ext * gsu_ext = pm->u.hash->tmpdata;
+
+    /* Uses normal unsetter. Will delete all owned
+     * parameters and also hashtable. */
+    pm->gsu.h->setfn(pm, NULL);
+
+    /* Don't need custom GSU structure with its
+     * redisContext pointer anymore */
+    zsfree(gsu_ext->redis_host_port);
+    if (gsu_ext->password)
+        zsfree(gsu_ext->password);
+    zsfree(gsu_ext->key);
+    zfree(gsu_ext, sizeof(struct gsu_scalar_ext));
+
+    pm->node.flags |= PM_UNSET;
+}
+/* }}} */
+/* FUNCTION: redis_hash_hset_untie {{{ */
+/**/
+static void
+redis_hash_hset_untie(Param pm)
+{
+    redisContext *rc = ((struct gsu_scalar_ext *)pm->u.hash->tmpdata)->rc;
+    HashTable ht = pm->u.hash;
+
+    if (rc) { /* paranoia */
+        redisFree(rc);
+
+        /* Let hash fields know there's no backend */
+        ((struct gsu_scalar_ext *)ht->tmpdata)->rc = NULL;
+
+        /* Remove from list of tied parameters */
+        remove_tied_name(pm->node.nam);
+    }
+
+    /* for completeness ... createspecialhash() should have an inverse */
+    ht->getnode = ht->getnode2 = gethashnode2;
+    ht->scantab = NULL;
+
+    pm->node.flags &= ~(PM_SPECIAL|PM_READONLY);
+    pm->gsu.h = &stdhash_gsu;
+}
+/* }}} */
+
+/****************** LIST *****************/
+
+/* FUNCTION: redis_arrlist_getfn {{{ */
+
+/**/
+char **
+redis_arrlist_getfn(Param pm)
+{
+    struct gsu_array_ext *gsu_ext;
+    char *key;
+    size_t key_len;
+    redisContext *rc;
+    redisReply *reply;
+    int j;
+
+    gsu_ext = (struct gsu_array_ext *) pm->gsu.a;
+    /* Key already retrieved? */
+    if ((pm->node.flags & PM_UPTODATE) && gsu_ext->use_cache) {
+        return pm->u.arr ? pm->u.arr : &my_nullarray;
+    }
+
+    key = gsu_ext->key;
+    key_len = gsu_ext->key_len;
+
+    int retry = 0;
+ retry:
+    rc = gsu_ext->rc;
+
+    if (!rc)
+        return &my_nullarray;
+
+    reply = redisCommand(rc, "EXISTS %b", key, (size_t) key_len);
+    if (reply && reply->type == REDIS_REPLY_INTEGER && reply->integer == 1) {
+        freeReplyObject(reply);
+        reply = NULL;
+
+        reply = redisCommand(rc, "LRANGE %b 0 -1", key, (size_t) key_len);
+        if (reply && reply->type == REDIS_REPLY_ARRAY) {
+            /* We have data – store it and return it */
+            pm->node.flags |= PM_UPTODATE;
+
+            /* Ensure there's no leak */
+            if (pm->u.arr) {
+                freearray(pm->u.arr);
+                pm->u.arr = NULL;
+            }
+
+            pm->u.arr = zalloc((reply->elements + 1) * sizeof(char*));
+
+            for (j = 0; j < reply->elements; j++) {
+                if (NULL == reply->element[j]) {
+                    pm->u.arr[j] = ztrdup("");
+                    zwarn("Error 8 when fetching elements");
+                    continue;
+                } else if (reply->element[j]->type != REDIS_REPLY_STRING) {
+                    pm->u.arr[j] = ztrdup("");
+                    if (NULL != reply->element[j]->str && reply->element[j]->len > 0) {
+                        zwarn("Error 9 when fetching elements (message: %s)", reply->element[j]->str);
+                    } else {
+                        zwarn("Error 9 when fetching elements");
+                    }
+                    continue;
+                }
+                /* Metafy returned data. All fits - metafy
+                 * can obtain data length to avoid using \0 */
+                pm->u.arr[j] = metafy(reply->element[j]->str,
+                                      reply->element[j]->len,
+                                      META_DUP);
+            }
+            pm->u.arr[reply->elements] = NULL;
+
+            freeReplyObject(reply);
+            reply = NULL;
+
+            /* Can return pointer, correctly saved inside Param */
+            return pm->u.arr;
+        } else if (reply) {
+            freeReplyObject(reply);
+            reply = NULL;
+        }
+    } else if (reply) {
+        freeReplyObject(reply);
+        reply = NULL;
+    }
+
+    /* Disconnect detection */
+    if (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF)) {
+        if (reply) {
+            freeReplyObject(reply);
+            reply = NULL;
+        }
+        if (retry) {
+            zwarn("Aborting (no connection)");
+            return &my_nullarray;
+        }
+        retry = 1;
+        if(reconnect(&gsu_ext->rc, gsu_ext->redis_host_port, gsu_ext->password))
+            goto retry;
+    }
+
+    /* Array with 0 elements */
+    return &my_nullarray;
+}
+/* }}} */
+/* FUNCTION: redis_arrlist_setfn {{{ */
+/**/
+mod_export void
+redis_arrlist_setfn(Param pm, char **val)
+{
+    char *key, *content;
+    size_t key_len, content_len;
+    int alen = 0, j;
+    redisContext *rc;
+    redisReply *reply;
+
+    /* Set is done on parameter and on database. */
+
+    /* Parameter */
+    if (pm->u.arr && pm->u.arr != val) {
+        freearray(pm->u.arr);
+        pm->u.arr = NULL;
+        pm->node.flags &= ~(PM_UPTODATE);
+    }
+
+    if (val) {
+        alen = arrlen(val);
+        pm->u.arr = val;
+        pm->node.flags |= PM_UPTODATE;
+    }
+
+    /* Database */
+    struct gsu_array_ext *gsu_ext = (struct gsu_array_ext *) pm->gsu.a;
+    rc = gsu_ext->rc;
+    key = gsu_ext->key;
+    key_len = gsu_ext->key_len;
+
+    if (rc) {
+        int retry = 0;
+    retry:
+        rc = gsu_ext->rc;
+
+        if (!rc)
+            return;
+
+        reply = redisCommand(rc, "DEL %b", key, (size_t) key_len);
+        if (reply) {
+            freeReplyObject(reply);
+            reply = NULL;
+        }
+
+        /* Disconnect detection */
+        if (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF)) {
+            if (retry) {
+                zwarn("Aborting (no connection)");
+                return;
+            }
+            retry = 1;
+            if(reconnect(&gsu_ext->rc, gsu_ext->redis_host_port, gsu_ext->password))
+                goto retry;
+            else
+                return;
+        }
+
+        if (val) {
+            for (j=0; j<alen; j ++) {
+                /* Unmetafy with exact zalloc size */
+                int umlen = 0;
+                char *umval = unmetafy_zalloc(val[j], &umlen);
+
+                /* Store */
+                content = umval;
+                content_len = umlen;
+                reply = redisCommand(rc, "RPUSH %b %b", key, (size_t) key_len, content, (size_t) content_len);
+                if (reply) {
+                    freeReplyObject(reply);
+                    reply = NULL;
+                }
+                if (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF)) {
+                    break;
+                }
+
+                /* Free */
+                set_length(umval, umlen);
+                zsfree(umval);
+            }
+
+            /* Disconnect detection */
+            if (rc->err & (REDIS_ERR_IO | REDIS_ERR_EOF)) {
+                if (retry) {
+                    zwarn("Aborting (no connection)");
+                    return;
+                }
+                retry = 1;
+                if(reconnect(&gsu_ext->rc, gsu_ext->redis_host_port, gsu_ext->password))
+                    goto retry;
+            }
+        }
+    }
+
+    if (pm->ename && val)
+        arrfixenv(pm->ename, val);
+}
+/* }}} */
+/* FUNCTION: redis_arrlist_unsetfn {{{ */
+/**/
+mod_export void
+redis_arrlist_unsetfn(Param pm, UNUSED(int exp))
+{
+    /* Will clear the database */
+    redis_arrlist_setfn(pm, NULL);
+
+    /* Will detach from database and free custom memory */
+    redis_arrlist_untie(pm);
+
+    pm->node.flags |= PM_UNSET;
+}
+/* }}} */
+/* FUNCTION: redis_arrlist_untie {{{ */
+/**/
+static void
+redis_arrlist_untie(Param pm)
+{
+    struct gsu_array_ext *gsu_ext = (struct gsu_array_ext *) pm->gsu.a;
+
+    if (gsu_ext->rc) /* paranoia */
+        redisFree(gsu_ext->rc);
+
+    /* Remove from list of tied parameters */
+    remove_tied_name(pm->node.nam);
+
+    pm->node.flags &= ~(PM_SPECIAL|PM_READONLY);
+    pm->gsu.a = &stdarray_gsu;
+
+    /* Free gsu_ext */
+    zsfree(gsu_ext->redis_host_port);
+    if (gsu_ext->password)
+        zsfree(gsu_ext->password);
+    zsfree(gsu_ext->key);
+    zfree(gsu_ext, sizeof(struct gsu_array_ext));
+}
+/* }}} */
+
+/*************** MAIN CODE ***************/
+
+/* ARRAY features {{{ */
+static struct features module_features = {
+    bintab, sizeof(bintab)/sizeof(*bintab),
+    NULL, 0,
+    NULL, 0,
+    patab, sizeof(patab)/sizeof(*patab),
+    0
+};
+/* }}} */
+
+/* FUNCTION: setup_ {{{ */
+/**/
+int
+setup_(UNUSED(Module m))
+{
+    return 0;
+}
+/* }}} */
+/* FUNCTION: features_ {{{ */
+/**/
+int
+features_(Module m, char ***features)
+{
+    *features = featuresarray(m, &module_features);
+    return 0;
+}
+/* }}} */
+/* FUNCTION: enables_ {{{ */
+/**/
+int
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m, &module_features, enables);
+}
+/* }}} */
+/* FUNCTION: boot_ {{{ */
+/**/
+int
+boot_(UNUSED(Module m))
+{
+    zredis_tied = zshcalloc((1) * sizeof(char *));
+    return 0;
+}
+/* }}} */
+/* FUNCTION: cleanup_ {{{ */
+/**/
+int
+cleanup_(Module m)
+{
+    /* This frees `zredis_tied` */
+    return setfeatureenables(m, &module_features, NULL);
+}
+/* }}} */
+/* FUNCTION: finish_ {{{ */
+/**/
+int
+finish_(UNUSED(Module m))
+{
+    return 0;
+}
+/* }}} */
+
+/**************** UTILITY ****************/
+
+/* FUNCTION: createhash {{{ */
+static Param createhash(char *name, int flags, int which)
+{
+    Param pm;
+    HashTable ht;
+
+    pm = createparam(name, flags | PM_SPECIAL | PM_HASHED);
+    if (!pm) {
+        return NULL;
+    }
+
+    if (pm->old)
+        pm->level = locallevel;
+
+    /* This creates standard hash. */
+    ht = pm->u.hash = newparamtable(32, name);
+    if (!pm->u.hash) {
+        paramtab->removenode(paramtab, name);
+        paramtab->freenode(&pm->node);
+        zwarnnam(name, "Out of memory when allocating hash");
+        return NULL;
+    }
+
+    /* Does free Param (unsetfn is called) */
+    ht->freenode = myfreeparamnode;
+
+    /* These provide special features */
+    if ( which == 0 ) {
+        ht->getnode = ht->getnode2 = redis_get_node;
+        ht->scantab = scan_keys;
+    } else if ( which == 1 ) {
+        ht->getnode = ht->getnode2 = redis_zset_get_node;
+        ht->scantab = zset_scan_keys;
+    } else if ( which == 2 ) {
+        ht->getnode = ht->getnode2 = redis_hset_get_node;
+        ht->scantab = hset_scan_keys;
+    } else {
+        return NULL;
+    }
+
+    return pm;
+}
+/* }}} */
+/* FUNCTION: append_tied_name {{{ */
+
+/*
+ * Adds parameter name to `zredis_tied`
+ */
+
+static int append_tied_name(const char *name)
+{
+    int old_len = arrlen(zredis_tied);
+    char **new_zredis_tied = zshcalloc((old_len+2) * sizeof(char *));
+
+    /* Copy */
+    char **p = zredis_tied;
+    char **dst = new_zredis_tied;
+    while (*p) {
+        *dst++ = *p++;
+    }
+
+    /* Append new one */
+    *dst = ztrdup(name);
+
+    /* Substitute, free old one */
+    zfree(zredis_tied, sizeof(char *) * (old_len + 1));
+    zredis_tied = new_zredis_tied;
+
+    return 0;
+}
+/* }}} */
+/* FUNCTION: remove_tied_name {{{ */
+
+/*
+ * Removes parameter name from `zredis_tied`
+ */
+
+static int remove_tied_name(const char *name)
+{
+    int old_len = arrlen(zredis_tied);
+
+    /* Two stage, to always have arrlen() == zfree-size - 1.
+     * Could do allocation and revert when `not found`, but
+     * what would be better about that. */
+
+    /* Find one to remove */
+    char **p = zredis_tied;
+    while (*p) {
+        if (0==strcmp(name,*p)) {
+            break;
+        }
+        p++;
+    }
+
+    /* Copy x+1 to x */
+    while (*p) {
+        *p=*(p+1);
+        p++;
+    }
+
+    /* Second stage. Size changed? Only old_size-1
+     * change is possible, but.. paranoia way */
+    int new_len = arrlen(zredis_tied);
+    if (new_len != old_len) {
+        char **new_zredis_tied = zshcalloc((new_len+1) * sizeof(char *));
+
+        /* Copy */
+        p = zredis_tied;
+        char **dst = new_zredis_tied;
+        while (*p) {
+            *dst++ = *p++;
+        }
+        *dst = NULL;
+
+        /* Substitute, free old one */
+        zfree(zredis_tied, sizeof(char *) * (old_len + 1));
+        zredis_tied = new_zredis_tied;
+    }
+
+    return 0;
+}
+/* }}} */
+/* FUNCTION: unmetafy_zalloc {{{ */
+
+/*
+ * Unmetafy that:
+ * - duplicates bufer to work on it,
+ * - does zalloc of exact size for the new string,
+ * - restores work buffer to original content, to restore strlen
+ *
+ * No zsfree()-confusing string will be produced.
+ */
+static char *unmetafy_zalloc(const char *to_copy, int *new_len)
+{
+    char *work, *to_return;
+    int my_new_len = 0;
+
+    work = ztrdup(to_copy);
+    work = unmetafy(work, &my_new_len);
+
+    if (new_len)
+        *new_len = my_new_len;
+
+    /* This string can be correctly zsfree()-d */
+    to_return = (char *) zalloc((my_new_len+1)*sizeof(char));
+    memcpy(to_return, work, sizeof(char)*my_new_len); // memcpy handles $'\0'
+    to_return[my_new_len]='\0';
+
+    /* Restore original strlen and correctly free */
+    strcpy(work, to_copy);
+    zsfree(work);
+
+    return to_return;
+}
+/* }}} */
+/* FUNCTION: set_length {{{ */
+/*
+ * For zsh-allocator, rest of Zsh seems to use
+ * free() instead of zsfree(), and such length
+ * restoration causes slowdown, but all is this
+ * way strict - correct */
+static void set_length(char *buf, int size) {
+    buf[size]='\0';
+    while (-- size >= 0) {
+        buf[size]=' ';
+    }
+}
+/* }}} */
+/* FUNCTION: parse_host_string {{{ */
+static void parse_host_string(const char *input, char *resource_name, int size,
+                                char **host, int *port, int *db_index, char **key)
+{
+    strncpy(resource_name, input, size-1);
+    resource_name[size-1] = '\0';
+
+    /* Parse -f argument */
+    char *processed = resource_name;
+    char *port_start, *key_start, *needle;
+    if ((port_start = strchr(processed, ':'))) {
+        if (port_start[1] != '\0') {
+            if ((needle = strchr(port_start+1, '/'))) {
+                /* Port with following database index */
+                *needle = '\0';
+                if (port_start[1] != '\0')
+                    *port = atoi(port_start + 1);
+                processed = needle+1;
+            } else {
+                /* Port alone */
+                *port = atoi(port_start + 1);
+                processed = NULL;
+            }
+        } else {
+            /* Empty port, nothing follows */
+            processed = NULL;
+        }
+
+        /* Process host name */
+        *port_start = '\0';
+        if (resource_name[0] != '\0')
+            *host = resource_name;
+    } else {
+        /* No-port track */
+        if ((needle = strchr(processed, '/'))) {
+            *needle = '\0';
+            *host = resource_name;
+            processed = needle+1;
+        }
+    }
+
+    /* In this place host name and port are already parsed */
+
+    /* Database index */
+    if (processed) {
+        if ((key_start = strchr(processed, '/'))) {
+            /* With key-following track */
+            *key_start = '\0';
+            if (processed[0] != '\0')
+                *db_index = atoi(processed);
+            processed = key_start + 1;
+        } else {
+            /* Without key-following track */
+            if (processed[0] != '\0')
+                *db_index = atoi(processed);
+            processed = NULL;
+        }
+    }
+
+    /* Key */
+    if (processed) {
+        if (processed[0] != '\0')
+            *key = processed;
+    }
+}
+/* }}} */
+/* FUNCTION: connect {{{ */
+static int connect(char *nam, redisContext **rc, const char* password, const char *host, int port,
+                   int db_index, const char *resource_name_in)
+{
+    redisReply *reply;
+
+    /* Connect */
+    struct timeval timeout = { 1, 500000 }; // 1.5 seconds
+    *rc = redisConnectWithTimeout(host, port, timeout);
+
+    if(*rc == NULL || (*rc)->err != 0) {
+        if(rc) {
+            if(nam && nam[0] != '\0') {
+              zwarnnam(nam, "error opening database %s:%d/%d (%s)", host, port, db_index, (*rc)->errstr);
+            } else {
+              zwarn("error opening database %s:%d/%d (%s)", host, port, db_index, (*rc)->errstr);
+            }
+            // redisFree(*rc);
+            // *rc = NULL;
+        } else {
+            if(nam && nam[0] != '\0') {
+                zwarnnam(nam, "error opening database %s (insufficient memory)", resource_name_in);
+            } else {
+                zwarn("error opening database %s (insufficient memory)", resource_name_in);
+            }
+        }
+        return 0;
+    }
+
+    /* Authenticate */
+    if (password) {
+        if (!auth(rc, password)) {
+            // redisFree(*rc);
+            // *rc = NULL;
+            return 0;
+        }
+    }
+
+    /* Select database */
+    if (db_index) {
+        reply = redisCommand(*rc, "SELECT %d", db_index);
+        if (reply == NULL || reply->type == REDIS_REPLY_ERROR) {
+            if (reply) {
+                if(nam && nam[0] != '\0') {
+                    zwarnnam(nam, "error selecting database #%d (host: %s:%d, message: %s)", db_index, host, port, reply->str);
+                } else {
+                    zwarn("error selecting database #%d (host: %s:%d, message: %s)", db_index, host, port, reply->str);
+                }
+                freeReplyObject(reply);
+            } else {
+                if(nam && nam[0] != '\0') {
+                    zwarnnam(nam, "IO error selecting database #%d (host: %s:%d)", db_index, host, port);
+                } else {
+                    zwarn("IO error selecting database #%d (host: %s:%d)", db_index, host, port);
+                }
+            }
+            // redisFree(*rc);
+            // *rc = NULL;
+            return 0;
+        }
+        freeReplyObject(reply);
+    }
+
+    return 1;
+}
+/* }}} */
+/* FUNCTION: type {{{ */
+static int type(redisContext **rc, const char *redis_host_port, const char *password, char *key, size_t key_len) {
+    redisReply *reply = NULL;
+
+    int retry = 0;
+ retry:
+    if (!*rc) {
+        return RD_TYPE_UNKNOWN;
+    }
+
+    reply = redisCommand(*rc, "TYPE %b", key, (size_t) key_len);
+    if (reply == NULL || reply->type != REDIS_REPLY_STATUS) {
+        if (reply) {
+            freeReplyObject(reply);
+            reply = NULL;
+        }
+        if ((*rc)->err & (REDIS_ERR_IO | REDIS_ERR_EOF)) {
+            goto do_retry;
+        } else {
+            return RD_TYPE_UNKNOWN;
+        }
+    }
+
+ do_retry:
+    /* Disconnect detection */
+    if ((*rc)->err & (REDIS_ERR_IO | REDIS_ERR_EOF)) {
+        if (retry) {
+            zwarn("Aborting (no connection)");
+            return RD_TYPE_UNKNOWN;
+        }
+        retry = 1;
+        if(reconnect(rc, redis_host_port, password))
+            goto retry;
+        else
+            return RD_TYPE_UNKNOWN;
+    }
+
+
+    if (0 == strncmp("string", reply->str, reply->len)) {
+        freeReplyObject(reply);
+        return RD_TYPE_STRING;
+    }
+    if (0 == strncmp("list", reply->str, reply->len)) {
+        freeReplyObject(reply);
+        return RD_TYPE_LIST;
+    }
+    if (0 == strncmp("set", reply->str, reply->len)) {
+        freeReplyObject(reply);
+        return RD_TYPE_SET;
+    }
+    if (0 == strncmp("zset", reply->str, reply->len)) {
+        freeReplyObject(reply);
+        return RD_TYPE_ZSET;
+    }
+    if (0 == strncmp("hash", reply->str, reply->len)) {
+        freeReplyObject(reply);
+        return RD_TYPE_HASH;
+    }
+    freeReplyObject(reply);
+    return RD_TYPE_UNKNOWN;
+}
+/* }}} */
+/* FUNCTION: auth {{{ */
+static int auth(redisContext **rc, const char *password) {
+    redisReply *reply;
+
+    if (!password || password[0] == '\0')
+        return 1;
+
+    reply = redisCommand(*rc, "AUTH %b", password, (size_t)strlen(password));
+    if (reply == NULL || reply->type == REDIS_REPLY_ERROR) {
+        if (reply) {
+            zwarn("Error when authenticating (%s)", reply->str);
+            freeReplyObject(reply);
+        } else {
+            zwarn("Error when authenticating (no connection?)");
+        }
+        return 0;
+    }
+    freeReplyObject(reply);
+    return 1;
+}
+/* }}} */
+/* FUNCTION: is_tied {{{ */
+static int is_tied(Param pm) {
+    if (pm->gsu.h == &redis_hash_gsu) {
+        return 1;
+    } else if (pm->gsu.s->getfn == &redis_str_getfn) {
+        return 1;
+    } else if (pm->gsu.a->getfn == &redis_arrset_getfn) {
+        return 1;
+    } else if (pm->gsu.h == &hash_zset_gsu) {
+        return 1;
+    } else if (pm->gsu.h == &hash_hset_gsu) {
+        return 1;
+    } else if (pm->gsu.a->getfn == &redis_arrlist_getfn) {
+        return 1;
+    }
+
+    return 0;
+}
+/* }}} */
+/* FUNCTION: zrtie_usage {{{ */
+static void zrtie_usage() {
+    fprintf(stdout, YELLOW "Usage:" RESET " zrtie -d db/redis [-p] [-r] [-a password] " MAGENTA "-f {host-spec}"
+            RESET " " RED "{parameter_name}" RESET "\n");
+    fprintf(stdout, YELLOW "Options:" RESET "\n");
+    fprintf(stdout, GREEN " -d" RESET ": select database type, can change in future, currently only \"db/redis\"\n");
+    fprintf(stdout, GREEN " -p" RESET ": passthrough - always do a fresh query to database, don't use cache\n");
+    fprintf(stdout, GREEN " -r" RESET ": create read-only parameter\n" );
+    fprintf(stdout, GREEN " -f" RESET ": database-address in format {host}[:port][/[db_idx][/key]]\n");
+    fprintf(stdout, GREEN " -a" RESET ": database-password to be used with AUTH (redis command)\n");
+    fprintf(stdout, GREEN " -A" RESET ": file with database-password to be used with AUTH (redis command)\n");
+    fprintf(stdout, "The " RED "{parameter_name}" RESET " - choose name for the created database-bound parameter\n");
+    fflush(stdout);
+}
+/* }}} */
+/* FUNCTION: zrzset_usage {{{ */
+static void zrzset_usage() {
+    fprintf(stdout, YELLOW "Usage:" RESET " zrzset {tied-param-name}\n");
+    fprintf(stdout, YELLOW "Output:" RESET " $reply array, to hold elements of the sorted set\n");
+    fflush(stdout);
+}
+/* }}} */
+/* FUNCTION: zruntie_usage {{{ */
+static void zruntie_usage() {
+    fprintf(stdout, YELLOW "Usage:" RESET " zruntie [-u] {tied-variable-name} [tied-variable-name] ...\n");
+    fprintf(stdout, YELLOW "Options:" RESET "\n");
+    fprintf(stdout, GREEN " -u" RESET ": Allow to untie read-only parameter\n");
+    fprintf(stdout, YELLOW "Description:" RESET " detaches variable from database and removes the variable;\n");
+    fprintf(stdout, YELLOW "            " RESET " database is not cleared\n");
+    fflush(stdout);
+}
+/* }}} */
+/* FUNCTION: zredishost_usage {{{ */
+static void zredishost_usage() {
+    fprintf(stdout, YELLOW "Usage:" RESET " zredishost {tied-variable-name}\n");
+    fprintf(stdout, YELLOW "Description:" RESET " stores host-spec of given variable to $REPLY\n");
+    fflush(stdout);
+}
+/* }}} */
+/* FUNCTION: zredisclear_usage {{{ */
+static void zredisclear_usage() {
+    fprintf(stdout, YELLOW "Usage:" RESET " zredisclear {tied-variable-name} [key name]\n");
+    fprintf(stdout, YELLOW "Description:" RESET " clears cache of given hash/key or of given plain\n");
+    fprintf(stdout, YELLOW "            " RESET " variable: set (array), list (array), string (scalar);\n");
+    fprintf(stdout, YELLOW "            " RESET " pass `-p' option to zrtie to disable cache for variable\n");
+    fflush(stdout);
+}
+/* }}} */
+/* FUNCTION: reconnect {{{ */
+static int reconnect(redisContext **rc, const char *hostspec_in, const char *password) {
+    char hostspec[192];
+    char *host="127.0.0.1", *key="";
+    int port = 6379, db_index = 0;
+
+    parse_host_string(hostspec_in, hostspec, 192, &host, &port, &db_index, &key);
+
+    redisFree(*rc);
+    *rc = NULL;
+    if(!connect("", rc, password, host, port, db_index, hostspec_in)) {
+        zwarn("Not connected, retrying... Failed, aborting");
+        return 0;
+    } else {
+        zwarn("Not connected, retrying... Success");
+        return 1;
+    }
+}
+/* }}} */
+/* FUNCTION: myfreeparamnode {{{ */
+static void
+myfreeparamnode(HashNode hn)
+{
+    Param pm = (Param) hn;
+ 
+    /* Upstream: The second argument of unsetfn() is used by modules to
+     * differentiate "exp"licit unset from implicit unset, as when
+     * a parameter is going out of scope.  It's not clear which
+     * of these applies here, but passing 1 has always worked.
+     */
+
+    /* if (delunset) */
+      pm->gsu.s->unsetfn(pm, 1);
+
+    zsfree(pm->node.nam);
+    /* If this variable was tied by the user, ename was ztrdup'd */
+    if (pm->node.flags & PM_TIED && pm->ename) {
+        zsfree(pm->ename);
+        pm->ename = NULL;
+    }
+    zfree(pm, sizeof(struct param));
+}
+/* }}} */
+
+#else
+# error no hiredis library after it was correctly detected (by configure script)
+#endif /* have hiredis */
diff --git a/Src/Modules/db_redis.mdd b/Src/Modules/db_redis.mdd
new file mode 100644
index 0000000..ca9ef5e
--- /dev/null
+++ b/Src/Modules/db_redis.mdd
@@ -0,0 +1,12 @@
+name=zsh/db/redis
+link='if test "x$ac_cv_lib_hiredis_redisConnect" = xyes && test "x$ac_cv_header_hiredis_hiredis_h" = xyes; then
+  echo dynamic
+else
+  echo no
+fi
+'
+load=no
+
+autofeatures="b:zrtie b:zruntie b:zredishost b:zredisclear b:zrzset p:zredis_tied"
+
+objects="db_redis.o"
diff --git a/configure.ac b/configure.ac
index 88da89e..322ce68 100644
--- a/configure.ac
+++ b/configure.ac
@@ -451,6 +451,10 @@ AC_ARG_ENABLE(gdbm,
 AC_HELP_STRING([--disable-gdbm], [turn off search for gdbm library]),
 [gdbm="$enableval"], [gdbm=yes])
 
+AC_ARG_ENABLE(redis,
+AC_HELP_STRING([--disable-redis], [turn off search for hiredis library]),
+[redis="$enableval"], [redis=yes])
+
 dnl ------------------
 dnl CHECK THE COMPILER
 dnl ------------------
@@ -979,6 +983,11 @@ if test x$gdbm != xno; then
   AC_CHECK_LIB(gdbm, gdbm_open)
 fi
 
+if test x$redis != xno; then
+  AC_CHECK_HEADERS(hiredis/hiredis.h)
+  AC_CHECK_LIB(hiredis, redisConnect)
+fi
+
 AC_CHECK_HEADERS(sys/xattr.h)
 
 dnl --------------
@@ -1319,7 +1328,7 @@ AC_CHECK_FUNCS(strftime strptime mktime timelocal \
 	       grantpt unlockpt ptsname \
 	       htons ntohs \
 	       regcomp regexec regerror regfree \
-	       gdbm_open getxattr \
+	       gdbm_open redisConnect getxattr \
 	       realpath canonicalize_file_name \
 	       symlink getcwd \
 	       cygwin_conv_path \

[-- Attachment #3: V12db_redis.ztst --]
[-- Type: application/octet-stream, Size: 7796 bytes --]

# Tests for the zdharma/redis module

%prep

 module_path=( `pwd`/Modules )
 modname="zsh/db/redis"
 db1="127.0.0.1:6379/10"
 db2="127.0.0.1/11"
 if ! zmodload $modname ; then
   ZTST_unimplemented="can't load $modname module for testing"
 fi
 redis-cli -n 10 flushdb
 redis-cli -n 11 flushdb

%test

 (zmodload -u $modname && zmodload $modname)
0:unload and reload the module without crashing

 zrtie -d db/redis -f $db1 dbase
 dbase[testkey]=testdata
 zruntie dbase
 unset dbase
 zrtie -r -d db/redis -f $db1 dbase
 echo $dbase[testkey]
 zruntie -u dbase
 echo $zredis_tied ${#zredis_tied}
0:store key in database 1
>testdata
>0

 zrtie -d db/redis -f $db2 dbase
 dbase[testkey]=testdata
 zruntie dbase
 unset dbase
 zrtie -d db/redis -f $db2 dbase
 echo $dbase[testkey]
 zruntie dbase
 echo $zredis_tied ${#zredis_tied}
0:store key in database 2
>testdata
>0

 zrtie -d db/redis -f $db1 dbase2
 unset 'dbase2[testkey]'
 zruntie dbase2
 zrtie -d db/redis -f $db1 dbase
 echo $dbase[testkey]
 zruntie dbase
 echo $zredis_tied ${#zredis_tied}
0:remove key from database 1 (different variables)
>
>0

 zrtie -d db/redis -f $db1 dbase
 dbase[testkey]=testdata
 zruntie dbase
 zrtie -d db/redis -f $db1 dbase
 echo $dbase[testkey]
 zruntie dbase
 zrtie -d db/redis -f $db1 dbase
 unset 'dbase[testkey]'
 zruntie dbase
 zrtie -r -d db/redis -f $db1 dbase
 echo $dbase[testkey]
 zruntie -u dbase
 echo $zredis_tied ${#zredis_tied}
0:store & remove key from database (the same variables)
>testdata
>
>0

 zrtie -d db/redis -f $db1 dbase
 dbase[testkey]=testdata
 dbase[testkey2]=$dbase[testkey]
 dbase[testkey3]=$dbase[testkey]x$dbase[testkey2]
 zruntie dbase
 zrtie -d db/redis -f $db1 dbase
 echo $dbase[testkey]
 echo $dbase[testkey2]
 echo $dbase[testkey3]
 zruntie dbase
 echo $zredis_tied ${#zredis_tied}
0:store 2 keys fetching 1st
>testdata
>testdata
>testdataxtestdata
>0

 zrtie -d db/redis -f $db1 dbase
 val=$dbase[testkey2]
 unset 'dbase[testkey2]'
 echo $val
 zruntie dbase
 echo $zredis_tied ${#zredis_tied}
0:unset key that was fetched
>testdata
>0

 zrtie -d db/redis -f $db1 dbase
 local -a result
 result=( "${(kv)dbase[@]}" )
 print -rl -- "${(o)result[@]}"
 zruntie dbase
 echo $zredis_tied ${#zredis_tied}
0:scan read-only tied hash, directly assign local -a
>testdata
>testdataxtestdata
>testkey
>testkey3
>0

 zrtie -d db/redis -f $db1 dbase
 dbase=( a a )
 print -rl -- "${(kv)dbase[@]}"
 zruntie dbase
 echo $zredis_tied ${#zredis_tied}
0:Use scan directly after overwrite of database
>a
>a
>0

 zrtie -d db/redis -f $db1 dbase
 dbase=( a b c d )
 zruntie dbase
 unset dbase
 zrtie -r -d db/redis -f $db1 dbase
 result=( "${(kv)dbase[@]}" )
 print -rl -- "${(o)result[@]}"
 zruntie -u dbase
 echo $zredis_tied ${#zredis_tied}
0:replace hash / database with untie, scan
>a
>b
>c
>d
>0

 zrtie -d db/redis -f $db1 dbase
 local -a arr
 arr=( "${dbase[@]}" )
 print -rl -- "${(o)arr[@]}"
 zruntie dbase
 echo $zredis_tied ${#zredis_tied}
0:scan with no (kv)
>b
>d
>0

 zrtie -d db/redis -f $db1 dbase
 result=( "${(k)dbase[@]}" )
 print -rl -- "${(o)result[@]}"
 zruntie dbase
 echo $zredis_tied ${#zredis_tied}
0:scan with keys only - (k)
>a
>c
>0

 zrtie -d db/redis -f $db1 dbase
 result=( "${(v)dbase[@]}" )
 print -rl -- "${(o)result[@]}"
 zruntie dbase
 echo $zredis_tied ${#zredis_tied}
0:scan with values only, explicit - (v)
>b
>d
>0

 zrtie -d db/redis -f ${db1%/*}/1000 dbase 2>/dev/null; ret=$?
 echo $zredis_tied ${#zredis_tied}
 return $ret
1:read-only open non-existent database
>0

 zrtie -d db/redis -f $db1 dbase
 dbase=( )
 dbase+=( a b )
 echo $dbase[a]
 zruntie dbase
 zrtie -d db/redis -f $db1 dbase
 echo $dbase[a]
 result=( "${(kv)dbase[@]}" )
 print -rl -- "${(o)result[@]}"
 zruntie dbase
 zrtie -d db/redis -f $db1 dbase
 dbase+=( c d )
 echo $dbase[a]
 echo $dbase[c]
 result=( "${(kv)dbase[@]}" )
 print -rl -- "${(o)result[@]}"
 zruntie dbase
 zrtie -d db/redis -f $db1 dbase
 echo $dbase[a]
 echo $dbase[c]
 result=( "${(kv)dbase[@]}" )
 print -rl -- "${(o)result[@]}"
 zruntie dbase
0:Append with +=( ), also with existing data, also (kv) scan
>b
>b
>a
>b
>b
>d
>a
>b
>c
>d
>b
>d
>a
>b
>c
>d

 zrtie -d db/redis -f $db1 dbase
 echo ${(t)dbase}
 zruntie dbase
0:Type of tied parameter
>association-special

 typeset -ga dbase
 zrtie -d db/redis -f $db1 dbase
 echo ${(t)dbase}
 zruntie dbase
0:Type of tied parameter, with preceding unset
>association-special

 local -a dbase
 zrtie -d db/redis -f $db1 dbase
 echo ${(t)dbase}
 zruntie dbase
0:Type of tied parameter, with local parameter already existing
>association-local-special

 local -a dbase
 dbase=( fromarray )
 () {
     local -a dbase
     zrtie -d db/redis -f $db1 dbase
     echo ${(t)dbase}
     zruntie dbase
 }
 echo $dbase[1]
 zrtie -d db/redis -f $db1 dbase
 echo "Can connect:" $dbase[a]
 zruntie dbase
0:Test of automatic untie (use of local scope) and of scoping
>association-local-special
>fromarray
>Can connect: b

 echo First $zredis_tied ${#zredis_tied}
 zrtie -d db/redis -f $db1 dbase
 echo $zredis_tied ${#zredis_tied}
 zrtie -d db/redis -f $db2 dbase2
 echo $zredis_tied ${#zredis_tied}
 zruntie dbase
 echo $zredis_tied ${#zredis_tied}
 zruntie dbase2
 echo $zredis_tied ${#zredis_tied}
0:zredis_tied parameter
>First 0
>dbase 1
>dbase dbase2 2
>dbase2 1
>0

 unset zredis_tied 2>/dev/null
1:unset of read-only zgdbm_tied parameter

 zrtie -d db/redis -f $db1 dbase
 dbase[漢字]=漢字
 echo $dbase[漢字]
 zruntie dbase
 zrtie -d db/redis -f $db1 dbase
 echo $dbase[漢字]
 zruntie -u dbase
0:Unicode test
>漢字
>漢字

 key="ab"$'\0'"ef"
 zrtie -d db/redis -f $db1 dbase
 dbase[$key]=value
 echo $dbase[$key]
 zruntie dbase
 zrtie -d db/redis -f $db1 dbase
 echo $dbase[$key]
 zruntie dbase
 zrtie -d db/redis -f $db1 dbase
 dbase[$key]=$key
 zruntie dbase
 zrtie -d db/redis -f $db1 dbase
 [[ "$dbase[$key]" = "$key" ]] && echo correct
 zruntie dbase
0:Metafication of $'\0'
>value
>value
>correct

 zrtie -d db/redis -f $db1 dbase
 dbase=( 漢字 漢字 )
 echo $dbase[漢字]
 zruntie dbase
 zrtie -d db/redis -f $db1 dbase
 echo $dbase[漢字]
 zruntie dbase
 key="ab"$'\0'"ef"
 zrtie -d db/redis -f $db1 dbase
 dbase+=( $key $key )
 zruntie dbase
 zrtie -d db/redis -f $db1 dbase
 [[ "$dbase[$key]" = "$key" ]] && echo correct
 zruntie dbase
0:Unicode & metafication test, different hash access
>漢字
>漢字
>correct

 zrtie -d db/redis -f $db1 dbase
 dbase=( 漢字 漢字 )
 zruntie dbase
 zrtie -d db/redis -f $db1 dbase
 noglob print -rl ${(kv)dbase[@]}
 zruntie dbase
0:Hash scanning and metafication
>漢字
>漢字

 zrtie -d db/redis -f $db1 dbase
 noglob print -rl ${(okv)dbase[@]}
 zruntie dbase
0:Sorted hash scanning and metafication
>漢字
>漢字

 zrtie -d db/redis -f $db1 dbase
 zredishost dbase
 [[ $REPLY = "127.0.0.1:6379/10" ]] && echo correct
 zruntie dbase
 zrtie -d db/redis -f $db2 dbase
 zredishost dbase
 [[ $REPLY = "127.0.0.1/11" ]] && echo correct
 zruntie dbase
0:zredishost builtin
>correct
>correct

 zrtie -d db/redis -f $db1 dbase
 dbase[testkey]=value1
 fun() { while read line; do echo $line; done }
 eval "dbase[testkey]=value2" | fun
 echo $dbase[testkey]
 zredisclear dbase testkey
 echo $dbase[testkey]
 zruntie dbase
0:Test store in forked Zsh
>value1
>value2

 zrtie -p -d db/redis -f $db1 dbase
 dbase[testkey]=value1
 echo $dbase[testkey]
 fun() { while read line; do echo $line; done }
 eval "dbase[testkey]=value2" | fun
 echo $dbase[testkey]
 zruntie dbase
0:Test store in forked Zsh, with -p passthrough
>value1
>value2

 zrtie -d db/redis -f $db1 dbase
 echo $dbase[a] $dbase[b] $dbase[c]
 unset dbase
 zrtie -r -d db/redis -f $db1 dbase
 echo $dbase[a] $dbase[b] $dbase[c]
 zruntie -u dbase
0:Test references to not-existing elements
>
>

%clean

 redis-cli -n 10 flushdb
 redis-cli -n 11 flushdb

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: Add redis-db module to upstream?
  2017-06-03 19:35   ` Daniel Shahaf
  2017-06-04  5:57     ` Sebastian Gniazdowski
@ 2017-06-06  2:28     ` Eric Cook
  2017-06-07  9:29     ` Oliver Kiddle
  2 siblings, 0 replies; 12+ messages in thread
From: Eric Cook @ 2017-06-06  2:28 UTC (permalink / raw)
  To: zsh-workers

On 06/03/2017 03:35 PM, Daniel Shahaf wrote:
> Bart Schaefer wrote on Sat, 03 Jun 2017 10:09 -0700:
>> I have no objection to including this module's functionality in the base
>> distribution.  However, I don't think we should just chuck it in there
>> exactly as-is.
> 
> This module was created 10 days ago.  It has had no bug reports, no pull
> requests, and no code contributors other than its author.  Does it have
> any users besides Sebastian?  Will anyone on this list use this module
> in his setup?
> 
And in the event of the author burning out again, like he mentioned on irc
before, would anyone else care to maintain it.


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: Add redis-db module to upstream?
  2017-06-03 19:35   ` Daniel Shahaf
  2017-06-04  5:57     ` Sebastian Gniazdowski
  2017-06-06  2:28     ` Eric Cook
@ 2017-06-07  9:29     ` Oliver Kiddle
  2017-06-07 11:08       ` Sebastian Gniazdowski
                         ` (2 more replies)
  2 siblings, 3 replies; 12+ messages in thread
From: Oliver Kiddle @ 2017-06-07  9:29 UTC (permalink / raw)
  To: zsh-workers

On 3 Jun, Daniel Shahaf wrote:
> > I have no objection to including this module's functionality in the base
> > distribution.  However, I don't think we should just chuck it in there
> > exactly as-is.
>
> This module was created 10 days ago.  It has had no bug reports, no pull
> requests, and no code contributors other than its author.  Does it have
> any users besides Sebastian?  Will anyone on this list use this module
> in his setup?

I'm not sure that those points are entirely fair because building zsh modules
separate from the zsh tree is not trivial. It's a significant barrier
both to any potential contributor trying the module and to long term
usage as a separate module. If it was easy then it might be better to
keep such modules separate but it isn't.

Certrainly we want to take care that the hiredis dependency isn't making
a zsh build more complicated. Perhaps the module could be disabled by
default. I'd also agree with the points made by Bart such as unifying
ztie, perhaps with a generic typeset option: typeset -o file=...

As for use, I've never actually got around to trying the gdbm module.
What is it mostly good for? Very large variables or sharing variables?

Oliver


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: Add redis-db module to upstream?
  2017-06-07  9:29     ` Oliver Kiddle
@ 2017-06-07 11:08       ` Sebastian Gniazdowski
  2017-06-07 13:04       ` Daniel Shahaf
  2017-06-07 22:10       ` Bart Schaefer
  2 siblings, 0 replies; 12+ messages in thread
From: Sebastian Gniazdowski @ 2017-06-07 11:08 UTC (permalink / raw)
  To: zsh-workers, Oliver Kiddle

On 7 czerwca 2017 at 11:35:43, Oliver Kiddle (okiddle@yahoo.co.uk) wrote:
> Certrainly we want to take care that the hiredis dependency isn't making
> a zsh build more complicated. Perhaps the module could be disabled by
> default. I'd also agree with the points made by Bart such as unifying
> ztie, perhaps with a generic typeset option: typeset -o file=...

I copied gdbm checks and switched them to hiredis library. I feel OK with this because hiredis is small C library. Linking it feels cool, no large, awkward linkage, like it would maybe be for Oracle Berkley DB library, which isn't distributed in Homebrew nor other package systems (as Google suggests), and it felt large when testing it.

> As for use, I've never actually got around to trying the gdbm module.
> What is it mostly good for? Very large variables or sharing variables?

I was planing to use it with Zplugin, to build textual UI with ZUI library, to configure plugins this way. The problem is that plugins aren't that complex to ship TUI for them. One option however is important – enable/disable of plugin. I do this often, and it doesn't seem right to comment and uncomment things in Zshrc that often. So in general gdbm could be good to map some configuration on it, because database allows programmatic updating (but besides plugins, alterations of zshrc rather aren't often; but I suspect a user with different mindset than mine could quickly provide examples of useful mapping).

When developing zredis, it became clear that embedded databases aren't that useful. Having a server that separates files on disc from clients – this opens more possibilities. There are two other free NoSQL databases sophia and tarantool, the last one was created by Mail.ru for a social service, it might be a gem. The first use case is administrating the databases, but I think other ones should appear.

--
Sebastian Gniazdowski
psprint /at/ zdharma.org


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: Add redis-db module to upstream?
  2017-06-07  9:29     ` Oliver Kiddle
  2017-06-07 11:08       ` Sebastian Gniazdowski
@ 2017-06-07 13:04       ` Daniel Shahaf
  2017-06-07 22:10       ` Bart Schaefer
  2 siblings, 0 replies; 12+ messages in thread
From: Daniel Shahaf @ 2017-06-07 13:04 UTC (permalink / raw)
  To: zsh-workers

Oliver Kiddle wrote on Wed, 07 Jun 2017 11:29 +0200:
> On 3 Jun, Daniel Shahaf wrote:
> > > I have no objection to including this module's functionality in the base
> > > distribution.  However, I don't think we should just chuck it in there
> > > exactly as-is.
> >
> > This module was created 10 days ago.  It has had no bug reports, no pull
> > requests, and no code contributors other than its author.  Does it have
> > any users besides Sebastian?  Will anyone on this list use this module
> > in his setup?
> 
> I'm not sure that those points are entirely fair because building zsh modules
> separate from the zsh tree is not trivial. It's a significant barrier
> both to any potential contributor trying the module and to long term
> usage as a separate module. If it was easy then it might be better to
> keep such modules separate but it isn't.

Then let's make it easier for people to develop and install separately-
maintained modules.

As to keeping such modules separate, this _is_ git, so module developers
could maintain their modules in forks of zsh.git, and users could
install modules by merging all modules they want to use into their tree
before running Util/preconfig.  I don't consider this a solution; only a
workaround until the core's build system is improved to make it easier
to use externally-maintained modules.

The build system improvement is independent of whether the redis module
should be in the core.

Cheers,

Daniel


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: Add redis-db module to upstream?
  2017-06-07  9:29     ` Oliver Kiddle
  2017-06-07 11:08       ` Sebastian Gniazdowski
  2017-06-07 13:04       ` Daniel Shahaf
@ 2017-06-07 22:10       ` Bart Schaefer
  2 siblings, 0 replies; 12+ messages in thread
From: Bart Schaefer @ 2017-06-07 22:10 UTC (permalink / raw)
  To: zsh-workers

On Jun 7, 11:29am, Oliver Kiddle wrote:
}
} Certrainly we want to take care that the hiredis dependency isn't making
} a zsh build more complicated. Perhaps the module could be disabled by
} default.

I believe this can be taken care of by configure, in the same way that
gdbm and pcre are left uncompiled if configure can't find them.

} I'd also agree with the points made by Bart such as unifying
} ztie, perhaps with a generic typeset option: typeset -o file=...

I think a separate builtin like ztie is the right way to go here,
rather than further overloading typeset.  We need a way to select
the module (ztie -d ...) and to specify external objects (ztie -f
and possibly other options if the notion of a "file" doesn't match
the usage).

} As for use, I've never actually got around to trying the gdbm module.
} What is it mostly good for? Very large variables or sharing variables?

Mostly it's for providing a shell-level interface to anything you would
store in a simple attibute-value database.  Specifically to zsh it can
be used to share state among shells (for example IF we could rely on it
gdbm woule be a much better file store for _store_cache/_retrieve_cache
than the current mechanism of serializing arrays).

If I understand redis's built-in clustering mechanism correctly, it
could be used to share state among shells on different hosts as well.


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: Add redis-db module to upstream?
@ 2017-06-08  4:01 Sebastian Gniazdowski
  0 siblings, 0 replies; 12+ messages in thread
From: Sebastian Gniazdowski @ 2017-06-08  4:01 UTC (permalink / raw)
  To: Bart Schaefer, zsh-workers

On 8 czerwca 2017 at 00:10:20, Bart Schaefer (schaefer@brasslantern.com) wrote:
> rather than further overloading typeset. We need a way to select
> the module (ztie -d ...) and to specify external objects (ztie -f

Current option set:

Usage: ztie -d db/... [-z] [-r] [-p password] [-P password_file] -f/-a {db_address} {parameter_name}
Options:
 -d:       select database type: "db/gdbm", "db/redis"
 -z:       zero-cache for read operations (always access database)
 -r:       create read-only parameter
 -f or -a: database-address in format {host}[:port][/[db_idx][/key]] or a file path
 -p:       database-password to be used for authentication
 -P:       path to file with database-password

Also, -l for "load password", for the password mechanism. Not so cool mnemonic ("load"), maybe a better one can be invented.

> store in a simple attibute-value database. Specifically to zsh it can
> be used to share state among shells (for example IF we could rely on it

I heard fish has the universal variables, shared among sessions. Too bad I don't know how to do typeset -F SECONDS=0 there, I would compare performance there too:

echo ${#redis_list}; typeset -F SECONDS=0; repeat 1000; do local -a copy=( $redis_list ); done; echo $SECONDS
1100
1.4957760000

local_list=( $redis_list )
typeset -F SECONDS=0; repeat 1000; do local -a copy=( $local_list ); done; echo $SECONDS
0.5719420000

So not that bad, x2.6. For array of 5 elements:

typeset -F SECONDS=0; repeat 1000; do local -a copy=( $redis_list ); done; echo $SECONDS
0.1843630000
typeset -F SECONDS=0; repeat 1000; do local -a copy=( $local_list ); done; echo $SECONDS
0.0125850000

That's 14.6 times slower, but only 0.000184s per access. So not even a 1 ms time. This was for system under load (compilation of Emacs), without load the 1100 results are the same, while for 5 elements, it's: 0.1277 vs. 0.009106, x14, 0.0001277 per access.

Cool idea about the clustering mechanism.

--
Sebastian Gniazdowski
psprint /at/ zdharma.org


^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2017-06-09 16:12 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-06-03  3:24 Add redis-db module to upstream? Sebastian Gniazdowski
2017-06-03 17:09 ` Bart Schaefer
2017-06-03 19:35   ` Daniel Shahaf
2017-06-04  5:57     ` Sebastian Gniazdowski
2017-06-04  6:23       ` Sebastian Gniazdowski
2017-06-06  2:28     ` Eric Cook
2017-06-07  9:29     ` Oliver Kiddle
2017-06-07 11:08       ` Sebastian Gniazdowski
2017-06-07 13:04       ` Daniel Shahaf
2017-06-07 22:10       ` Bart Schaefer
2017-06-04  8:35   ` Sebastian Gniazdowski
2017-06-08  4:01 Sebastian Gniazdowski

Code repositories for project(s) associated with this public inbox

	https://git.vuxu.org/mirror/zsh/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).