zsh-workers
 help / color / mirror / code / Atom feed
* [PATCH] 2 modules, zsh/db, zsh/gdbm, bug-fixes
@ 2017-06-22  9:04 Sebastian Gniazdowski
  2017-06-22  9:05 ` Sebastian Gniazdowski
  2017-06-23  8:36 ` Bart Schaefer
  0 siblings, 2 replies; 15+ messages in thread
From: Sebastian Gniazdowski @ 2017-06-22  9:04 UTC (permalink / raw)
  To: zsh-workers

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

Hello
1. New module `zsh/db` that provides good architecture, via `*_main_entry` functions in client modules (zsh/db/gdbm in this patch).

2. Also, `zdb_backends` hash holding e.g. "db/gdbm" -> "loaded". 

These are according to Bart opinion. Amount of builtins is normal, small (ztie, zuntie, etc). Also, zsh/db behaves as a library of common functions.

When developing zredis, with intensive Valgrind use, I've found and fixed following bugs in zsh/gdbm:

- no deletehashtable() in custom hash-set-fn,

- no emptyhashtable() in custom hash-set-fn – replacing hash with tied_param=( k v ) caused addition of k -> v, and left untouched previous Param content,

- call to *untie function to remove PM_READONLY flag caused memory leak, the flag is now removed directly, if user gave -u option to untie,

- data returned by libgdbm should be free()-d (e.g. key returned by gdbm_nextkey),

- freeparamnode() had if(delunset) condition, this caused memory leak – replaced with myfreeparamnode().

Tested on: OS X, Freebsd, Gentoo, Ubuntu.

Next step would be zredis.

--  
Sebastian Gniazdowski
psprint /at/ zdharma.org

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

diff --git a/Src/Modules/db.c b/Src/Modules/db.c
new file mode 100644
index 0000000..1af0fc2
--- /dev/null
+++ b/Src/Modules/db.c
@@ -0,0 +1,799 @@
+/* -*- Mode: C; c-default-style: "linux"; c-basic-offset: 4; indent-tabs-mode: nil -*-
+ * vim:sw=4:sts=4:et
+ */
+
+/*
+ * db.c - general database module, forwards to backends
+ *
+ * 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.mdh"
+#include "db.pro"
+#include "db.h"
+
+/* MACROS {{{ */
+#ifndef PM_UPTODATE
+#define PM_UPTODATE     (1<<19) /* Parameter has up-to-date data (e.g. loaded from DB) */
+#endif
+/* }}} */
+/* DECLARATIONS {{{ */
+static void ztie_usage();
+static void zuntie_usage();
+static void ztaddress_usage();
+static void ztclear_usage();
+
+static HashTable createhashtable(char *name);
+static void freebackendnode(HashNode hn);
+static void backend_scan_fun(HashNode hn, int unused);
+
+static Param createhashparam(char *name, int flags);
+
+/* Type of provided (by backend module) entry-point */
+typedef int (*DbBackendEntryPoint)(VA_ALIST1(int cmd));
+
+struct backend_node {
+    struct hashnode node;
+    DbBackendEntryPoint main_entry;
+};
+
+typedef struct backend_node *BackendNode;
+
+/* Maps db/dbtype onto BackendNode */
+static HashTable backends_hash = NULL;
+
+/* For searching with scanhashtable */
+char *In_ParamName = NULL;
+DbBackendEntryPoint Out_FoundBe = NULL;
+/* }}} */
+/* ARRAY: builtin {{{ */
+static struct builtin bintab[] =
+{
+    /* h - help, d - backend type, r - read-only, a/f - address/file,
+     * l - load password from terminal, p - password as argument,
+     * P - password from file, z - zero read-cache */
+    BUILTIN("ztie", 0, bin_ztie, 0, -1, 0, "hrzf:d:a:p:P:L:", NULL),
+    BUILTIN("zuntie", 0, bin_zuntie, 0, -1, 0, "uh", NULL),
+    BUILTIN("ztaddress", 0, bin_ztaddress, 0, -1, 0, "h", NULL),
+    BUILTIN("ztclear", 0, bin_ztclear, 0, -1, 0, "h", NULL),
+};
+/* }}} */
+/* ARRAY: other {{{ */
+#define ROARRPARAMDEF(name, var)                                        \
+    { name, PM_ARRAY | PM_READONLY, (void *) var, NULL,  NULL, NULL, NULL }
+/* }}} */
+
+/* FUNCTION: update_user_hash {{{ */
+static void update_user_hash(char *id, int is_loading) {
+    Param pm = (Param) paramtab->getnode(paramtab, "zdb_backends");
+    if(!pm) {
+        zwarn("no such parameter: zdb_backends, internal error");
+        return;
+    }
+
+    /* Must be a special hash */
+    if (!(pm->node.flags & PM_HASHED) || !(pm->node.flags & PM_SPECIAL)) {
+        zwarn("zsh/db: Parameter zdb_backends is defined by user, will not update it");
+        return;
+    }
+
+    HashTable ht = pm->u.hash;
+    HashNode hn = gethashnode2(ht, id);
+    Param val_pm = (Param) hn;
+
+    if (val_pm) {
+        if (val_pm->u.str) {
+            zsfree(val_pm->u.str);
+            val_pm->u.str = NULL;
+        }
+        if (is_loading) {
+            val_pm->u.str = ztrdup("re-loaded");
+        } else {
+            val_pm->u.str = ztrdup("unloaded");
+        }
+    } else {
+        val_pm = (Param) zshcalloc(sizeof (*val_pm));
+        val_pm->node.flags = PM_SCALAR | PM_HASHELEM;
+        val_pm->gsu.s = &stdscalar_gsu;
+        if (is_loading) {
+            val_pm->u.str = ztrdup("loaded");
+        } else {
+            val_pm->u.str = ztrdup("unloaded");
+        }
+        addhashnode(ht, ztrdup(id), val_pm); // sets pm->node.nam
+    }
+}
+/* }}} */
+/* FUNCTION: zsh_db_register_backend {{{ */
+
+/**/
+void
+zsh_db_register_backend(char *id, void *entry_point) {
+    BackendNode bn = (BackendNode)zshcalloc(sizeof(struct backend_node));
+    if (bn) {
+        bn->main_entry = entry_point;
+        addhashnode(backends_hash, ztrdup(id), (void *)bn);
+    } else {
+        zwarn("Out of memory when allocating backend entry");
+        return;
+    }
+
+    update_user_hash(id, 1);
+}
+/* }}} */
+/* FUNCTION: zsh_db_unregister_backend {{{ */
+
+/**/
+void
+zsh_db_unregister_backend(char *id) {
+    HashNode bn = backends_hash->removenode(backends_hash, id);
+    if (bn) {
+        freebackendnode(bn);
+    }
+    update_user_hash(id, 0);
+}
+/* }}} */
+/* FUNCTION: bin_ztie {{{ */
+
+/**/
+static int
+bin_ztie(char *nam, char **args, Options ops, UNUSED(int func))
+{
+    char *pmname;
+
+    /* Check options */
+
+    if (OPT_ISSET(ops,'h')) {
+        ztie_usage();
+        return 0;
+    }
+
+    if (!OPT_ISSET(ops,'d')) {
+        zwarnnam(nam, "you must pass e.g. `-d db/redis', see `-h'");
+        return 1;
+    }
+
+    if (!OPT_ISSET(ops,'f') && !OPT_ISSET(ops,'a')) {
+        zwarnnam(nam, "you must pass `-f' (file, or `-a', address) with e.g. {host}[:port][/[db_idx][/key]], see `-h'");
+        return 1;
+    }
+
+    if (0 != strcmp(OPT_ARG(ops, 'd'), "db/redis") && 0 != strcmp(OPT_ARG(ops, 'd'), "db/gdbm") ) {
+        zwarnnam(nam, "Unsupported backend type `%s', see `-h'", OPT_ARG(ops, 'd'));
+        return 1;
+    }
+
+    /* Check argument */
+    pmname = *args;
+
+    if (!pmname) {
+        zwarnnam(nam, "You must pass non-option argument - the target parameter to create, see -h");
+        return 1;
+    }
+
+    /* Prepare arguments for backend */
+
+    char *address = NULL, *pass = NULL, *pfile = NULL, *lazy = NULL;
+    int rdonly = 0, zcache = 0, pprompt = 0;
+
+    /* Address */
+    if (OPT_ISSET(ops,'f')) {
+        address = OPT_ARG(ops,'f');
+    } else {
+        address = OPT_ARG(ops,'a');
+    }
+
+    /* Read-only */
+    if (OPT_ISSET(ops,'r')) {
+        rdonly = 1;
+    } else {
+        rdonly = 0;
+    }
+
+    /* Zero-cache */
+    if (OPT_ISSET(ops,'z')) {
+        zcache = 1;
+    } else {
+        zcache = 0;
+    }
+
+    /* Password */
+    if (OPT_ISSET(ops,'p')) {
+        pass = OPT_ARG(ops,'p');
+    }
+
+    /* Password file */
+    if (OPT_ISSET(ops,'P')) {
+        pfile = OPT_ARG(ops,'P');
+    }
+
+    /* Password load request */
+    if (OPT_ISSET(ops,'l')) {
+        pprompt = 1;
+    } else {
+        pprompt = 0;
+    }
+
+    /* Lazy binding */
+    if (OPT_ISSET(ops,'L')) {
+        lazy = OPT_ARG(ops,'L');
+    }
+
+    BackendNode node = NULL;
+    DbBackendEntryPoint be = NULL;
+
+    if(!(node = (BackendNode) gethashnode2(backends_hash, OPT_ARG(ops, 'd')))) {
+        zwarnnam(nam, "Backend module for %s not loaded (or loaded before the main `db' module)", OPT_ARG(ops, 'd'));
+        return 1;
+    }
+
+    be = node->main_entry;
+    if (!be) {
+        zwarnnam(nam, "Backend for %s is uninitialized", OPT_ARG(ops, 'd'));
+        return 1;
+    }
+
+    return be(DB_TIE, address, rdonly, zcache, pass, pfile, pprompt, pmname, lazy);
+}
+/* }}} */
+/* FUNCTION: bin_zuntie {{{ */
+
+/**/
+static int
+bin_zuntie(char *nam, char **args, Options ops, UNUSED(int func))
+{
+    char *pmname;
+    int ret = 0;
+
+    if (OPT_ISSET(ops,'h')) {
+        zuntie_usage();
+        return 0;
+    }
+
+    if (!*args) {
+        zwarnnam(nam, "At least one variable name is needed, see -h");
+        return 1;
+    }
+
+    for (pmname = *args; *args++; pmname = *args) {
+        In_ParamName = pmname;
+        Out_FoundBe = NULL;
+
+        scanhashtable(backends_hash, 0, 0, 0, backend_scan_fun, 0);
+
+        if (!Out_FoundBe) {
+            zwarnnam(nam, "Didn't recognize `%s' as a tied parameter", pmname);
+            continue;
+        }
+
+        int rountie = 0;
+
+        if (OPT_ISSET(ops,'u')) {
+            rountie = 1;
+        }
+
+        ret = Out_FoundBe(DB_UNTIE, rountie, pmname) ? 1 : ret;
+    }
+
+    return ret;
+}
+/* }}} */
+/* FUNCTION: bin_ztaddress {{{ */
+
+/**/
+static int
+bin_ztaddress(char *nam, char **args, Options ops, UNUSED(int func))
+{
+    char *pmname;
+    int ret = 0;
+
+    if (OPT_ISSET(ops,'h')) {
+        ztaddress_usage();
+        return 0;
+    }
+
+    if (!*args) {
+        zwarnnam(nam, "One parameter name is needed, see -h");
+        return 1;
+    }
+
+    pmname = *args;
+    In_ParamName = pmname;
+    Out_FoundBe = NULL;
+
+    scanhashtable(backends_hash, 0, 0, 0, backend_scan_fun, 0);
+
+    if (!Out_FoundBe) {
+        zwarnnam(nam, "Didn't recognize `%s' as a tied parameter", pmname);
+        return 1;
+    }
+
+    ret = Out_FoundBe(DB_GET_ADDRESS, pmname) ? 1 : ret;
+
+    return ret;
+}
+/* }}} */
+/* FUNCTION: bin_ztclear {{{ */
+
+/**/
+static int
+bin_ztclear(char *nam, char **args, Options ops, UNUSED(int func))
+{
+    char *pmname, *key;
+    int ret = 0;
+
+    if (OPT_ISSET(ops,'h')) {
+        ztclear_usage();
+        return 0;
+    }
+
+    if (!*args) {
+        zwarnnam(nam, "One-to-two parameters' names are needed, see -h");
+        return 1;
+    }
+
+    pmname = *args;
+    key = *(args+1);
+
+    In_ParamName = pmname;
+    Out_FoundBe = NULL;
+
+    scanhashtable(backends_hash, 0, 0, 0, backend_scan_fun, 0);
+
+    if (!Out_FoundBe) {
+        zwarnnam(nam, "Didn't recognize `%s' as a tied parameter", pmname);
+        return 1;
+    }
+
+    ret = Out_FoundBe(DB_CLEAR_CACHE, pmname, key) ? 1 : ret;
+
+    return ret;
+}
+/* }}} */
+
+/* FUNCTION: ztaddress_usage {{{ */
+
+static void
+ztaddress_usage()
+{
+    fprintf(stdout, "Usage: ztaddress {tied-parameter-name}\n");
+    fprintf(stdout, "Description: stores address used by given parameter to $REPLY\n");
+    fflush(stdout);
+}
+/* }}} */
+/* FUNCTION: ztclear_usage {{{ */
+
+static void
+ztclear_usage()
+{
+    fprintf(stdout, "Usage: ztclear {tied-parameter-name} [key name]\n");
+    fprintf(stdout, "Description: clears cache of given hash/key or of given plain\n");
+    fprintf(stdout, "             parameter: set (array), list (array), string (scalar);\n");
+    fprintf(stdout, "             pass `-z' to ztie to globally disable cache for parameter\n");
+    fflush(stdout);
+}
+/* }}} */
+
+/*************** MAIN CODE ***************/
+
+/* ARRAY features {{{ */
+static struct features module_features =
+{
+    bintab, sizeof(bintab)/sizeof(*bintab),
+    NULL, 0,
+    NULL, 0,
+    NULL, 0,
+    0
+};
+/* }}} */
+
+/* FUNCTION: setup_ {{{ */
+
+/**/
+int
+setup_(UNUSED(Module m))
+{
+    Param pm  = NULL;
+
+    /* Create private registration hash */
+    if (!(backends_hash = createhashtable("ZSH_BACKENDS"))) {
+        zwarn("Cannot create backend-register hash");
+        return 1;
+    }
+
+    /* Unset zdb_backends if it exists. Inherited from db_gdbm,
+     * it clears current scope, leaving any upper scope untouched,
+     * which can result in zdb_backends being local. Can be seen
+     * as a feature */
+    if ((pm = (Param)paramtab->getnode(paramtab, "zdb_backends")) && !(pm->node.flags & PM_UNSET)) {
+        pm->node.flags &= ~PM_READONLY;
+        if (unsetparam_pm(pm, 0, 1)) {
+            zwarn("Cannot properly manage scoping of variables");
+            return 1;
+        }
+    }
+
+    /* Create zdb_backends public hash that will hold state of backends */
+    pm = createhashparam("zdb_backends", PM_READONLY | PM_REMOVABLE);
+    if (NULL == pm) {
+        zwarn("Cannot create user backends-list hash parameter");
+        if (backends_hash) {
+            deletehashtable(backends_hash);
+            backends_hash = NULL;
+        }
+        return 1;
+    }
+    pm->gsu.h = &stdhash_gsu;
+
+    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))
+{
+    return 0;
+}
+/* }}} */
+/* FUNCTION: cleanup_ {{{ */
+
+/**/
+int
+cleanup_(Module m)
+{
+    /* This frees `zredis_tied` */
+    return setfeatureenables(m, &module_features, NULL);
+
+    if (backends_hash) {
+        deletehashtable(backends_hash);
+        backends_hash = NULL;
+    }
+}
+/* }}} */
+/* FUNCTION: finish_ {{{ */
+
+/**/
+int
+finish_(UNUSED(Module m))
+{
+    return 0;
+}
+/* }}} */
+
+/*************** UTILITIES ***************/
+
+/* FUNCTION: createhashtable {{{ */
+static HashTable
+createhashtable(char *name)
+{
+    HashTable ht;
+
+    ht = newhashtable(8, name, NULL);
+
+    ht->hash        = hasher;
+    ht->emptytable  = emptyhashtable;
+    ht->filltable   = NULL;
+    ht->cmpnodes    = strcmp;
+    ht->addnode     = addhashnode;
+    ht->getnode     = gethashnode2;
+    ht->getnode2    = gethashnode2;
+    ht->removenode  = removehashnode;
+    ht->disablenode = NULL;
+    ht->enablenode  = NULL;
+    ht->freenode    = freebackendnode;
+    ht->printnode   = NULL;
+
+    return ht;
+}
+/* }}} */
+/* FUNCTION: createhashparam {{{ */
+static Param
+createhashparam(char *name, int flags)
+{
+    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(7, name);
+    if (!pm->u.hash) {
+        paramtab->removenode(paramtab, name);
+        paramtab->freenode(&pm->node);
+        zwarnnam(name, "Out of memory when allocating user-visible hash of backends");
+        return NULL;
+    }
+
+    /* Does free Param (unsetfn is called) */
+    ht->freenode = zsh_db_freeparamnode;
+
+    return pm;
+}
+/* }}} */
+/* FUNCTION: freebackendnode {{{ */
+static void
+freebackendnode(HashNode hn)
+{
+    zsfree(hn->nam);
+    zfree(hn, sizeof(struct backend_node));
+}
+/* }}} */
+/* FUNCTION: zsh_db_freeparamnode {{{ */
+
+/**/
+void
+zsh_db_freeparamnode(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));
+}
+/* }}} */
+/* FUNCTION: backend_scan_fun {{{ */
+static void
+backend_scan_fun(HashNode hn, int unused)
+{
+    BackendNode bn = (BackendNode)hn;
+    DbBackendEntryPoint be = bn->main_entry;
+    if (!be) {
+        zwarn("Backend %s registered but uninitialized", hn->nam);
+        return;
+    }
+    /* 0 - shell true value */
+    if(0 == be(DB_IS_TIED, In_ParamName)) {
+        Out_FoundBe = be;
+    }
+}
+/* }}} */
+
+/*********** SHARED UTILITIES ***********/
+
+/* FUNCTION: zsh_db_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.
+ */
+
+/**/
+char *
+zsh_db_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: zsh_db_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 */
+
+/**/
+void
+zsh_db_set_length(char *buf, int size)
+{
+    buf[size]='\0';
+    while (-- size >= 0) {
+        buf[size]=' ';
+    }
+}
+/* }}} */
+/* FUNCTION: zsh_db_standarize_hash {{{ */
+
+/**/
+void
+zsh_db_standarize_hash(Param pm) {
+    if (0 == (pm->node.flags & PM_HASHED)) {
+        return;
+    }
+
+    pm->node.flags &= ~(PM_SPECIAL|PM_READONLY);
+    pm->gsu.h = &stdhash_gsu;
+
+    HashTable ht = pm->u.hash;
+
+    ht->hash        = hasher;
+    ht->emptytable  = emptyhashtable;
+    ht->filltable   = NULL;
+    ht->cmpnodes    = strcmp;
+    ht->addnode     = addhashnode;
+    ht->getnode     = gethashnode;
+    ht->getnode2    = gethashnode2;
+    ht->removenode  = removehashnode;
+    ht->disablenode = NULL;
+    ht->enablenode  = NULL;
+    ht->freenode    = zsh_db_freeparamnode;
+}
+/* }}} */
+/* FUNCTION: zsh_db_arr_append {{{ */
+
+/*
+ * Adds parameter name (to given `*_tied` array)
+ */
+
+/**/
+int
+zsh_db_arr_append(char ***arr, const char *input_s)
+{
+    int old_len = arrlen(*arr);
+    char **new_arr = zshcalloc( (old_len+2) * sizeof(char *));
+
+    /* Copy */
+    char **p = *arr;
+    char **dst = new_arr;
+    while (*p) {
+        *dst++ = *p++;
+    }
+
+    /* Append new one */
+    *dst = ztrdup(input_s);
+
+    /* Substitute, free old one */
+    zfree(*arr, sizeof(char *) * (old_len + 1));
+    *arr = new_arr;
+
+    return 0;
+}
+/* }}} */
+/* FUNCTION: zsh_db_filter_arr {{{ */
+
+/*
+ * Removes parameter name (from given `*_tied` array)
+ */
+
+/**/
+int
+zsh_db_filter_arr(char ***arr, const char *input_s)
+{
+    int old_len = arrlen(*arr);
+
+    /* 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 = *arr;
+    while (*p) {
+        if (0==strcmp(input_s,*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(*arr);
+    if (new_len != old_len) {
+        char **new_arr = (char **) zshcalloc((new_len+1) * sizeof(char *));
+
+        /* Copy */
+        p = *arr;
+        char **dst = new_arr;
+        while (*p) {
+            *dst++ = *p++;
+        }
+        *dst = NULL;
+
+        /* Substitute, free old one */
+        zfree(*arr, sizeof(char *) * (old_len + 1));
+        *arr = new_arr;
+    }
+
+    return 0;
+}
+/* }}} */
+
+/***************** USAGE *****************/
+
+/* FUNCTION: ztie_usage {{{ */
+
+static void
+ztie_usage()
+{
+    fprintf(stdout, "Usage: ztie -d db/... [-z] [-r] [-p password] [-P password_file] [-L type]"
+            "-f/-a {db_address} {parameter_name}\n");
+    fprintf(stdout, "Options:\n");
+    fprintf(stdout, " -d:       select database type: \"db/gdbm\", \"db/redis\"\n");
+    fprintf(stdout, " -z:       zero-cache for read operations (always access database)\n");
+    fprintf(stdout, " -r:       create read-only parameter\n" );
+    fprintf(stdout, " -f or -a: database-address in format {host}[:port][/[db_idx][/key]] or a file path\n");
+    fprintf(stdout, " -p:       database-password to be used for authentication\n");
+    fprintf(stdout, " -P:       path to file with database-password\n");
+    fprintf(stdout, " -L:       lazy binding - provide type of key to create if it doesn't exist "
+                    "(string, set, zset, hash, list)\n");
+    fprintf(stdout, "The {parameter_name} - choose name for the created database-bound parameter\n");
+    fflush(stdout);
+}
+/* }}} */
+/* FUNCTION: zuntie_usage {{{ */
+
+static void
+zuntie_usage()
+{
+    fprintf(stdout, "Usage: zuntie [-u] {tied-variable-name} [tied-variable-name] ...\n");
+    fprintf(stdout, "Options:\n");
+    fprintf(stdout, " -u: Allow to untie read-only parameter\n");
+    fprintf(stdout, "Description: detaches variable from its database and removes the variable;\n");
+    fprintf(stdout, "             database is not cleared (unlike when unset)\n");
+    fflush(stdout);
+}
+/* }}} */
+
diff --git a/Src/Modules/db.h b/Src/Modules/db.h
new file mode 100644
index 0000000..eb1183d
--- /dev/null
+++ b/Src/Modules/db.h
@@ -0,0 +1,29 @@
+/*
+ * db.h
+ *
+ * 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.
+ */
+
+/* Backend commands */
+#define DB_TIE 1
+#define DB_UNTIE 2
+#define DB_IS_TIED 3
+#define DB_GET_ADDRESS 4
+#define DB_CLEAR_CACHE 5
+
+/* Data types, created according to redis */
+#define DB_KEY_TYPE_NONE 0  /* unknown type because non-existent key */
+#define DB_KEY_TYPE_UNKNOWN 1
+#define DB_KEY_TYPE_NO_KEY 2
+#define DB_KEY_TYPE_STRING 3
+#define DB_KEY_TYPE_LIST 4
+#define DB_KEY_TYPE_SET 5
+#define DB_KEY_TYPE_ZSET 6
+#define DB_KEY_TYPE_HASH 7
diff --git a/Src/Modules/db.mdd b/Src/Modules/db.mdd
new file mode 100644
index 0000000..73ac83d
--- /dev/null
+++ b/Src/Modules/db.mdd
@@ -0,0 +1,7 @@
+name=zsh/db
+link=dynamic
+load=no
+
+autofeatures="b:ztie b:zuntie b:ztaddress b:ztclear"
+
+objects="db.o"
diff --git a/Src/Modules/db_gdbm.c b/Src/Modules/db_gdbm.c
index 0ab0fe7..5949e80 100644
--- a/Src/Modules/db_gdbm.c
+++ b/Src/Modules/db_gdbm.c
@@ -1,3 +1,7 @@
+/* -*- Mode: C; c-default-style: "linux"; c-basic-offset: 4; indent-tabs-mode: nil -*-
+ * vim:sw=4:sts=4:et
+ */
+
 /*
  * db_gdbm.c - bindings for gdbm
  *
@@ -33,16 +37,21 @@
 
 #include "db_gdbm.mdh"
 #include "db_gdbm.pro"
+#include "db.epro"
+#include "db.h"
 
+/* MACROS {{{ */
 #ifndef PM_UPTODATE
 #define PM_UPTODATE     (1<<19) /* Parameter has up-to-date data (e.g. loaded from DB) */
 #endif
-
+/* }}} */
+/* DECLARATIONS {{{ */
 static Param createhash( char *name, int flags );
-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 int is_tied_cmd(char *pmname);
+static int is_tied(Param pm);
+
+static int no_database_action = 0;
+/* }}} */
 
 /*
  * Make sure we have all the bits I'm using for memory mapping, otherwise
@@ -52,7 +61,7 @@ static void set_length(char *buf, int size);
 
 #include <gdbm.h>
 
-static char *backtype = "db/gdbm";
+/* ARRAY: GSU {{{ */
 
 /*
  * Longer GSU structure, to carry GDBM_FILE of owning
@@ -72,25 +81,26 @@ static char *backtype = "db/gdbm";
  */
 
 struct gsu_scalar_ext {
-    struct gsu_scalar std;
-    GDBM_FILE dbf;
+    struct gsu_scalar std; /* Size of three pointers */
+    int type;
+    int use_cache;
+    int is_lazy;
     char *dbfile_path;
+    char *key;
+    size_t key_len;
+    char *password;
+    int fdesc;
+    GDBM_FILE dbf; /* a pointer */
 };
 
 /* Source structure - will be copied to allocated one,
  * with `dbf` filled. `dbf` allocation <-> gsu allocation. */
 static const struct gsu_scalar_ext gdbm_gsu_ext =
-{ { gdbmgetfn, gdbmsetfn, gdbmunsetfn }, 0, 0 };
+    { { gdbmgetfn, gdbmsetfn, gdbmunsetfn }, 0, 0 };
 
 /**/
 static const struct gsu_hash gdbm_hash_gsu =
-{ hashgetfn, gdbmhashsetfn, gdbmhashunsetfn };
-
-static struct builtin bintab[] = {
-    BUILTIN("ztie", 0, bin_ztie, 1, -1, 0, "d:f:r", NULL),
-    BUILTIN("zuntie", 0, bin_zuntie, 1, -1, 0, "u", NULL),
-    BUILTIN("zgdbmpath", 0, bin_zgdbmpath, 1, -1, 0, "", NULL),
-};
+    { hashgetfn, gdbmhashsetfn, gdbmhashunsetfn };
 
 #define ROARRPARAMDEF(name, var) \
     { name, PM_ARRAY | PM_READONLY, (void *) var, NULL,  NULL, NULL, NULL }
@@ -98,157 +108,241 @@ static struct builtin bintab[] = {
 /* Holds names of all tied parameters */
 char **zgdbm_tied;
 
-static struct paramdef patab[] = {
-    ROARRPARAMDEF( "zgdbm_tied", &zgdbm_tied ),
-};
+static struct paramdef patab[] =
+    { ROARRPARAMDEF( "zgdbm_tied", &zgdbm_tied ), };
+/* }}} */
+
+/* FUNCTION: gdbm_main_entry {{{ */
+static int
+gdbm_main_entry(VA_ALIST1(int cmd))
+    VA_DCL
+{
+    char *address = NULL, *pass = NULL, *pfile = NULL, *pmname = NULL, *key = NULL, *lazy = NULL;
+    int rdonly = 0, zcache = 0, pprompt = 0, rountie = 0;
+
+    va_list ap;
+    VA_DEF_ARG(int cmd);
+
+    VA_START(ap, cmd);
+    VA_GET_ARG(ap, cmd, int);
+
+    switch (cmd) {
+    case DB_TIE:
+        /* Order is:
+         * -a/f address, char *
+         * -r read-only, int
+         * -z zero-cache, int
+         * -p password, char *
+         * -P file with password, char *
+         * -l prompt for password, int
+         * -L lazy binding type, char *
+         * parameter name, char *
+         */
+        address = va_arg(ap, char *);
+        rdonly = va_arg(ap, int);
+        zcache = va_arg(ap, int);
+        pass = va_arg(ap, char *);
+        pfile = va_arg(ap, char *);
+        pprompt = va_arg(ap, int);
+        pmname = va_arg(ap, char *);
+        lazy = va_arg(ap, char *);
+        return zgtie_cmd(address, rdonly, zcache, pass, pfile, pprompt, pmname, lazy);
+
+    case DB_UNTIE:
+        /* Order is:
+         * -u untie read only parameter, int
+         * parameter name, char *
+         */
+        rountie = va_arg(ap, int);
+        pmname = va_arg(ap, char *);
+        char *argv[2];
+        argv[0] = pmname;
+        argv[1] = NULL;
+        return zguntie_cmd(rountie, argv);
+
+    case DB_IS_TIED:
+        /* Order is:
+         * parameter name, char *
+         */
+        pmname = va_arg(ap, char *);
+        return is_tied_cmd(pmname);
+
+    case DB_GET_ADDRESS:
+        /* Order is:
+         * Parameter name, char *
+         */
+        pmname = va_arg(ap, char*);
+        return gdbmpath_cmd(pmname);
+
+    case DB_CLEAR_CACHE:
+        /* Order is:
+         * Parameter name, char *
+         * key name, char *
+         */
+        pmname = va_arg(ap, char*);
+        key = va_arg(ap, char*);
+        return gdbmclear_cmd(pmname, key);
+
+    default:
+#ifdef DEBUG
+        dputs("Bad command %d in redis_main_entry", cmd);
+#endif
+        break;
+    }
+    return 1;
+}
+/* }}} */
+/* FUNCTION: zgtie_cmd {{{ */
 
 /**/
 static int
-bin_ztie(char *nam, char **args, Options ops, UNUSED(int func))
+zgtie_cmd(char *address, int rdonly, int zcache, char *pass, char *pfile, int pprompt, char *pmname, char *lazy)
 {
-    char *resource_name, *pmname;
     GDBM_FILE dbf = NULL;
     int read_write = GDBM_SYNC, pmflags = PM_REMOVABLE;
     Param tied_param;
 
-    if(!OPT_ISSET(ops,'d')) {
-        zwarnnam(nam, "you must pass `-d %s'", backtype);
-	return 1;
-    }
-    if(!OPT_ISSET(ops,'f')) {
-        zwarnnam(nam, "you must pass `-f' with a filename", NULL);
-	return 1;
-    }
-    if (OPT_ISSET(ops,'r')) {
-	read_write |= GDBM_READER;
-	pmflags |= PM_READONLY;
-    } else {
-	read_write |= GDBM_WRCREAT;
+    if (!address) {
+        zwarn("you must pass `-f' or '-a' path to the database", NULL);
+        return 1;
     }
 
-    /* Here should be a lookup of the backend type against
-     * a registry, if generam DB mechanism is to be added */
-    if (strcmp(OPT_ARG(ops, 'd'), backtype) != 0) {
-        zwarnnam(nam, "unsupported backend type `%s'", OPT_ARG(ops, 'd'));
-	return 1;
+    if (!pmname) {
+        zwarn("you must pass non-option argument - the target parameter to create, see -h");
+        return 1;
     }
 
-    resource_name = OPT_ARG(ops, 'f');
-    pmname = *args;
+    if (rdonly) {
+        read_write |= GDBM_READER;
+        pmflags |= PM_READONLY;
+    } else {
+        read_write |= GDBM_WRCREAT;
+    }
 
     if ((tied_param = (Param)paramtab->getnode(paramtab, pmname)) &&
-	!(tied_param->node.flags & PM_UNSET)) {
-	/*
-	 * 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
+        !(tied_param->node.flags & PM_UNSET)) {
+        /*
+         * 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;
+         *
+         * 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;
     }
 
     gdbm_errno=0;
-    dbf = gdbm_open(resource_name, 0, read_write, 0666, 0);
+    dbf = gdbm_open(address, 0, read_write, 0666, 0);
     if(dbf == NULL) {
-	zwarnnam(nam, "error opening database file %s (%s)", resource_name, gdbm_strerror(gdbm_errno));
-	return 1;
+        zwarn("error opening database file %s (%s)", address, gdbm_strerror(gdbm_errno));
+        return 1;
     }
 
     if (!(tied_param = createhash(pmname, pmflags))) {
-        zwarnnam(nam, "cannot create the requested parameter %s", pmname);
-	fdtable[gdbm_fdesc(dbf)] = FDT_UNUSED;
-	gdbm_close(dbf);
-	return 1;
+        zwarn("cannot create the requested parameter %s", pmname);
+        gdbm_close(dbf);
+        return 1;
     }
 
-    addmodulefd(gdbm_fdesc(dbf), FDT_MODULE);
-    append_tied_name(pmname);
-
     tied_param->gsu.h = &gdbm_hash_gsu;
 
-    /* Allocate parameter sub-gsu, fill dbf field. 
+    /* Allocate parameter sub-gsu, fill dbf field.
      * dbf allocation is 1 to 1 accompanied by
      * gsu_scalar_ext allocation. */
 
-    struct gsu_scalar_ext *dbf_carrier = (struct gsu_scalar_ext *) zalloc(sizeof(struct gsu_scalar_ext));
+    struct gsu_scalar_ext *dbf_carrier = (struct gsu_scalar_ext *) zshcalloc(sizeof(struct gsu_scalar_ext));
     dbf_carrier->std = gdbm_gsu_ext.std;
+    dbf_carrier->type = DB_KEY_TYPE_NO_KEY;
+    dbf_carrier->fdesc = gdbm_fdesc(dbf);
     dbf_carrier->dbf = dbf;
+    dbf_carrier->use_cache = 1;
+    if (zcache) {
+        dbf_carrier->use_cache = 0;
+    }
+    if (lazy) {
+        dbf_carrier->is_lazy = 1;
+    }
     tied_param->u.hash->tmpdata = (void *)dbf_carrier;
 
     /* Fill also file path field */
-    if (*resource_name != '/') {
+    if (*address != '/') {
         /* Code copied from check_autoload() */
-        resource_name = zhtricat(metafy(zgetcwd(), -1, META_HEAPDUP), "/", resource_name);
-        resource_name = xsymlink(resource_name, 1);
+        address = zhtricat(metafy(zgetcwd(), -1, META_HEAPDUP), "/", address);
+        address = xsymlink(address, 1);
     }
-    dbf_carrier->dbfile_path = ztrdup(resource_name);
+    dbf_carrier->dbfile_path = ztrdup(address);
+
+    addmodulefd(dbf_carrier->fdesc, FDT_INTERNAL);
+    zsh_db_arr_append(&zgdbm_tied, pmname);
+
     return 0;
 }
+/* }}} */
+/* FUNCTION: zguntie_cmd {{{ */
 
 /**/
 static int
-bin_zuntie(char *nam, char **args, Options ops, UNUSED(int func))
+zguntie_cmd(int rountie, char **args)
 {
     Param pm;
     char *pmname;
     int ret = 0;
 
     for (pmname = *args; *args++; pmname = *args) {
-	pm = (Param) paramtab->getnode(paramtab, pmname);
-	if(!pm) {
-	    zwarnnam(nam, "cannot untie %s", pmname);
-	    ret = 1;
-	    continue;
-	}
-	if (pm->gsu.h != &gdbm_hash_gsu) {
-	    zwarnnam(nam, "not a tied gdbm hash: %s", pmname);
-	    ret = 1;
-	    continue;
-	}
-
-	queue_signals();
-	if (OPT_ISSET(ops,'u'))
-	    gdbmuntie(pm);	/* clear read-only-ness */
-	if (unsetparam_pm(pm, 0, 1)) {
-	    /* assume already reported */
-	    ret = 1;
-	}
-	unqueue_signals();
+        pm = (Param) paramtab->getnode(paramtab, pmname);
+        if(!pm) {
+            zwarn("cannot untie %s", pmname);
+            ret = 1;
+            continue;
+        }
+        if (pm->gsu.h != &gdbm_hash_gsu) {
+            zwarn("not a tied gdbm hash: %s", pmname);
+            ret = 1;
+            continue;
+        }
+
+        queue_signals();
+        if (rountie) {
+            pm->node.flags &= ~PM_READONLY;
+        }
+        if (unsetparam_pm(pm, 0, 1)) {
+            /* assume already reported */
+            ret = 1;
+        }
+        unqueue_signals();
     }
 
     return ret;
 }
+/* }}} */
+/* FUNCTION: gdbmpath_cmd{{{ */
 
 /**/
 static int
-bin_zgdbmpath(char *nam, char **args, Options ops, UNUSED(int func))
+gdbmpath_cmd(char *pmname)
 {
     Param pm;
-    char *pmname;
-
-    pmname = *args;
 
     if (!pmname) {
-        zwarnnam(nam, "parameter name (whose path is to be written to $REPLY) is required");
+        zwarn("parameter name (whose path is to be written to $REPLY) not given");
         return 1;
     }
 
     pm = (Param) paramtab->getnode(paramtab, pmname);
     if(!pm) {
-        zwarnnam(nam, "no such parameter: %s", pmname);
+        zwarn("no such parameter: %s", pmname);
         return 1;
     }
 
     if (pm->gsu.h != &gdbm_hash_gsu) {
-        zwarnnam(nam, "not a tied gdbm parameter: %s", pmname);
+        zwarn("not a tied gdbm parameter: %s", pmname);
         return 1;
     }
 
@@ -261,6 +355,46 @@ bin_zgdbmpath(char *nam, char **args, Options ops, UNUSED(int func))
 
     return 0;
 }
+/* }}} */
+/* FUNCTION: bin_zgdbmclear {{{ */
+
+/**/
+static int
+gdbmclear_cmd(char *pmname, char *key)
+{
+    Param pm;
+
+    if (!pmname) {
+        zwarn("parameter name (whose read-cache is to be cleared) is required");
+        return 1;
+    }
+    if (!key) {
+        zwarn("hash-key (whose read-cache is to be cleared) is required");
+        return 1;
+    }
+
+    pm = (Param) paramtab->getnode(paramtab, pmname);
+    if(!pm) {
+        zwarnnam("no such parameter: %s", pmname);
+        return 1;
+    }
+
+    if (pm->gsu.h != &gdbm_hash_gsu) {
+        zwarnnam("not a tied gdbm parameter: %s", 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);
+    }
+
+    return 0;
+}
+/* }}} */
+/* FUNCTION: gdbmgetfn {{{ */
 
 /*
  * The param is actual param in hash – always, because
@@ -269,9 +403,7 @@ bin_zgdbmpath(char *nam, char **args, Options ops, UNUSED(int func))
  * 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, as no other writer
- * can exist. This would remove subtle hcalloc(1) leak.
+ * contain such key.
  */
 
 /**/
@@ -291,14 +423,14 @@ gdbmgetfn(Param pm)
      * - if we are writers, we for sure have newest copy of data
      * - if we are readers, we for sure have newest copy of data
      */
-    if ( pm->node.flags & PM_UPTODATE ) {
-        return pm->u.str ? pm->u.str : (char *) hcalloc(1);
+    if ((pm->node.flags & PM_UPTODATE) && ((struct gsu_scalar_ext *)pm->gsu.s)->use_cache) {
+        return pm->u.str ? pm->u.str : "";
     }
 
     /* Unmetafy key. GDBM fits nice into this
      * process, as it uses length of data */
     int umlen = 0;
-    char *umkey = unmetafy_zalloc(pm->node.nam,&umlen);
+    char *umkey = zsh_db_unmetafy_zalloc(pm->node.nam,&umlen);
 
     key.dptr = umkey;
     key.dsize = umlen;
@@ -319,9 +451,11 @@ gdbmgetfn(Param pm)
         /* Metafy returned data. All fits - metafy
          * can obtain data length to avoid using \0 */
         pm->u.str = metafy(content.dptr, content.dsize, META_DUP);
+        /* gdbm allocates with malloc */
+        free(content.dptr);
 
         /* Free key, restoring its original length */
-        set_length(umkey, umlen);
+        zsh_db_set_length(umkey, umlen);
         zsfree(umkey);
 
         /* Can return pointer, correctly saved inside hash */
@@ -329,12 +463,13 @@ gdbmgetfn(Param pm)
     }
 
     /* Free key, restoring its original length */
-    set_length(umkey, umlen);
+    zsh_db_set_length(umkey, umlen);
     zsfree(umkey);
 
-    /* Can this be "" ? */
-    return (char *) hcalloc(1);
+    return "";
 }
+/* }}} */
+/* FUNCTION: gdbmsetfn {{{ */
 
 /**/
 static void
@@ -361,16 +496,16 @@ gdbmsetfn(Param pm, char *val)
 
     /* Database */
     dbf = ((struct gsu_scalar_ext *)pm->gsu.s)->dbf;
-    if (dbf) {
+    if (dbf && no_database_action == 0) {
         int umlen = 0;
-        char *umkey = unmetafy_zalloc(pm->node.nam,&umlen);
+        char *umkey = zsh_db_unmetafy_zalloc(pm->node.nam,&umlen);
 
         key.dptr = umkey;
         key.dsize = umlen;
 
         if (val) {
             /* Unmetafy with exact zalloc size */
-            char *umval = unmetafy_zalloc(val,&umlen);
+            char *umval = zsh_db_unmetafy_zalloc(val,&umlen);
 
             /* Store */
             content.dptr = umval;
@@ -378,18 +513,19 @@ gdbmsetfn(Param pm, char *val)
             (void)gdbm_store(dbf, key, content, GDBM_REPLACE);
 
             /* Free */
-            set_length(umval, umlen);
+            zsh_db_set_length(umval, umlen);
             zsfree(umval);
         } else {
             (void)gdbm_delete(dbf, key);
         }
 
         /* Free key */
-        set_length(umkey, key.dsize);
+        zsh_db_set_length(umkey, key.dsize);
         zsfree(umkey);
     }
 }
-
+/* }}} */
+/* FUNCTION: gdbmunsetfn {{{ */
 /**/
 static void
 gdbmunsetfn(Param pm, UNUSED(int um))
@@ -397,6 +533,8 @@ gdbmunsetfn(Param pm, UNUSED(int um))
     /* Set with NULL */
     gdbmsetfn(pm, NULL);
 }
+/* }}} */
+/* FUNCTION: getgdbmnode {{{ */
 
 /**/
 static HashNode
@@ -432,12 +570,14 @@ getgdbmnode(HashTable ht, const char *name)
 
     return (HashNode) val_pm;
 }
+/* }}} */
+/* FUNCTION: scangdbmkeys {{{ */
 
 /**/
 static void
 scangdbmkeys(HashTable ht, ScanFunc func, int flags)
 {
-    datum key;
+    datum key, prev_key;
     GDBM_FILE dbf = ((struct gsu_scalar_ext *)ht->tmpdata)->dbf;
 
     /* Iterate keys adding them to hash, so
@@ -452,14 +592,18 @@ scangdbmkeys(HashTable ht, ScanFunc func, int flags)
         HashNode hn = getgdbmnode(ht, zkey);
         zsfree( zkey );
 
-	func(hn, flags);
+        func(hn, flags);
 
         /* Iterate - no problem as interfacing Param
          * will do at most only fetches, not stores */
+        prev_key = key;
         key = gdbm_nextkey(dbf, key);
+        free(prev_key.dptr);
     }
 
 }
+/* }}} */
+/* FUNCTION: gdbmhashsetfn {{{ */
 
 /*
  * Replace database with new hash
@@ -475,83 +619,99 @@ gdbmhashsetfn(Param pm, HashTable ht)
     datum key, content;
 
     if (!pm->u.hash || pm->u.hash == ht)
-	return;
+        return;
 
     if (!(dbf = ((struct gsu_scalar_ext *)pm->u.hash->tmpdata)->dbf))
-	return;
+        return;
 
     key = gdbm_firstkey(dbf);
     while (key.dptr) {
-	queue_signals();
-	(void)gdbm_delete(dbf, key);
-	free(key.dptr);
-	unqueue_signals();
-	key = gdbm_firstkey(dbf);
+        queue_signals();
+        (void)gdbm_delete(dbf, key);
+        free(key.dptr);
+        unqueue_signals();
+        key = gdbm_firstkey(dbf);
+    }
+
+    /* Just deleted everything, clean up.
+     * Turned off waiting for gdbm to fix NFS problem.
+     * User can reorganize via gdbmtool. */
+#if 0
+    if (GDBM_VERSION_MAJOR > 1 || (GDBM_VERSION_MAJOR == 1 && GDBM_VERSION_MINOR > 13) ) {
+        (void)gdbm_reorganize(dbf);
     }
+#endif
 
-    /* just deleted everything, clean up */
-    (void)gdbm_reorganize(dbf);
+    no_database_action = 1;
+    emptyhashtable(pm->u.hash);
+    no_database_action = 0;
 
     if (!ht)
-	return;
+        return;
 
-     /* Put new strings into database, waiting
-      * for their interfacing-Params to be created */
+    /* Put new strings into database, waiting
+     * for their interfacing-Params to be created */
 
-    for (i = 0; i < ht->hsize; i++)
-	for (hn = ht->nodes[i]; hn; hn = hn->next) {
-	    struct value v;
+    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;
+            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);
+            char *umkey = zsh_db_unmetafy_zalloc(v.pm->node.nam,&umlen);
 
-	    key.dptr = umkey;
-	    key.dsize = umlen;
+            key.dptr = umkey;
+            key.dsize = umlen;
 
-	    queue_signals();
+            queue_signals();
 
             /* Unmetafy */
-            char *umval = unmetafy_zalloc(getstrvalue(&v),&umlen);
+            char *umval = zsh_db_unmetafy_zalloc(getstrvalue(&v),&umlen);
 
             /* Store */
-	    content.dptr = umval;
-	    content.dsize = umlen;
-	    (void)gdbm_store(dbf, key, content, GDBM_REPLACE);	
+            content.dptr = umval;
+            content.dsize = umlen;
+            (void)gdbm_store(dbf, key, content, GDBM_REPLACE);
 
-            /* Free - unmetafy_zalloc allocates exact required
+            /* Free - zsh_db_unmetafy_zalloc allocates exact required
              * space, however unmetafied string can have zeros
              * in content, so we must first fill with non-0 bytes */
-            set_length(umval, content.dsize);
+            zsh_db_set_length(umval, content.dsize);
             zsfree(umval);
-            set_length(umkey, key.dsize);
+            zsh_db_set_length(umkey, key.dsize);
             zsfree(umkey);
 
-	    unqueue_signals();
-	}
+            unqueue_signals();
+        }
+    }
+    /* We reuse our hash, the input is to be deleted */
+    deleteparamtable(ht);
 }
+/* }}} */
+/* FUNCTION: gdbmuntie {{{*/
 
 /**/
 static void
 gdbmuntie(Param pm)
 {
-    GDBM_FILE dbf = ((struct gsu_scalar_ext *)pm->u.hash->tmpdata)->dbf;
+    struct gsu_scalar_ext *gsu_ext = (struct gsu_scalar_ext *)pm->u.hash->tmpdata;
+    GDBM_FILE dbf = gsu_ext->dbf;
     HashTable ht = pm->u.hash;
 
     if (dbf) { /* paranoia */
-	fdtable[gdbm_fdesc(dbf)] = FDT_UNUSED;
+        fdtable[gsu_ext->fdesc] = FDT_UNUSED;
         gdbm_close(dbf);
 
         /* Let hash fields know there's no backend */
         ((struct gsu_scalar_ext *)ht->tmpdata)->dbf = NULL;
 
         /* Remove from list of tied parameters */
-        remove_tied_name(pm->node.nam);
+        zsh_db_filter_arr(&zgdbm_tied, pm->node.nam);
     }
 
     /* for completeness ... createspecialhash() should have an inverse */
@@ -561,7 +721,8 @@ gdbmuntie(Param pm)
     pm->node.flags &= ~(PM_SPECIAL|PM_READONLY);
     pm->gsu.h = &stdhash_gsu;
 }
-
+/* }}} */
+/* FUNCTION: gdbmhashunsetfn {{{ */
 /**/
 static void
 gdbmhashunsetfn(Param pm, UNUSED(int exp))
@@ -572,8 +733,8 @@ gdbmhashunsetfn(Param pm, UNUSED(int exp))
      * 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. */
+    /* Uses normal unsetter (because gdbmuntie is called above).
+     * Will delete all owned field-parameters and also hashtable. */
     pm->gsu.h->setfn(pm, NULL);
 
     /* Don't need custom GSU structure with its
@@ -583,14 +744,19 @@ gdbmhashunsetfn(Param pm, UNUSED(int exp))
 
     pm->node.flags |= PM_UNSET;
 }
-
-static struct features module_features = {
-    bintab, sizeof(bintab)/sizeof(*bintab),
+/* }}} */
+/* ARRAY: module_features {{{ */
+static struct features module_features =
+{
+    NULL, 0,
     NULL, 0,
     NULL, 0,
     patab, sizeof(patab)/sizeof(*patab),
     0
 };
+/* }}} */
+
+/* FUNCTION: setup_ {{{ */
 
 /**/
 int
@@ -598,7 +764,8 @@ setup_(UNUSED(Module m))
 {
     return 0;
 }
-
+/* }}} */
+/* FUNCTION: features_ {{{ */
 /**/
 int
 features_(Module m, char ***features)
@@ -606,6 +773,8 @@ features_(Module m, char ***features)
     *features = featuresarray(m, &module_features);
     return 0;
 }
+/* }}} */
+/* FUNCTION: enables_ {{{ */
 
 /**/
 int
@@ -613,34 +782,45 @@ enables_(Module m, int **enables)
 {
     return handlefeatures(m, &module_features, enables);
 }
+/* }}} */
+/* FUNCTION: boot_ {{{ */
 
 /**/
 int
 boot_(UNUSED(Module m))
 {
     zgdbm_tied = zshcalloc((1) * sizeof(char *));
+    zsh_db_register_backend("db/gdbm", gdbm_main_entry);
     return 0;
 }
+/* }}} */
+/* FUNCTION: cleanup_ {{{ */
 
 /**/
 int
 cleanup_(Module m)
 {
+    zsh_db_unregister_backend("db/gdbm");
+
     /* This frees `zgdbm_tied` */
     return setfeatureenables(m, &module_features, NULL);
 }
-
+/* }}} */
+/* FUNCTION: finish_ {{{ */
 /**/
 int
 finish_(UNUSED(Module m))
 {
     return 0;
 }
+/* }}} */
 
 /*********************
  * Utility functions *
  *********************/
 
+/* FUNCTION: createhash {{{ */
+
 static Param createhash( char *name, int flags ) {
     Param pm;
     HashTable ht;
@@ -651,137 +831,51 @@ static Param createhash( char *name, int flags ) {
     }
 
     if (pm->old)
-	pm->level = locallevel;
+        pm->level = locallevel;
 
     /* This creates standard hash. */
-    ht = pm->u.hash = newparamtable(32, name);
+    ht = pm->u.hash = newparamtable(17, 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 = zsh_db_freeparamnode;
+
     /* These provide special features */
     ht->getnode = ht->getnode2 = getgdbmnode;
     ht->scantab = scangdbmkeys;
 
     return pm;
 }
-
-/*
- * Adds parameter name to `zgdbm_tied`
- */
-
-static int append_tied_name( const char *name ) {
-    int old_len = arrlen(zgdbm_tied);
-    char **new_zgdbm_tied = zshcalloc( (old_len+2) * sizeof(char *));
-
-    /* Copy */
-    char **p = zgdbm_tied;
-    char **dst = new_zgdbm_tied;
-    while (*p) {
-        *dst++ = *p++;
+/* }}} */
+/* FUNCTION: is_tied_cmd {{{ */
+static int
+is_tied_cmd(char *pmname)
+{
+    Param pm = (Param) paramtab->getnode(paramtab, pmname);
+    if(!pm) {
+        return 1; /* false */
     }
 
-    /* Append new one */
-    *dst = ztrdup(name);
-
-    /* Substitute, free old one */
-    zfree(zgdbm_tied, sizeof(char *) * (old_len + 1));
-    zgdbm_tied = new_zgdbm_tied;
-
-    return 0;
+    return 1 - is_tied(pm); /* negation for shell-code */
 }
+/* }}} */
+/* FUNCTION: is_tied {{{ */
 
-/*
- * Removes parameter name from `zgdbm_tied`
- */
-
-static int remove_tied_name( const char *name ) {
-    int old_len = arrlen(zgdbm_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 = zgdbm_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(zgdbm_tied);
-    if (new_len != old_len) {
-        char **new_zgdbm_tied = zshcalloc((new_len+1) * sizeof(char *));
-
-        /* Copy */
-        p = zgdbm_tied;
-        char **dst = new_zgdbm_tied;
-        while (*p) {
-            *dst++ = *p++;
-        }
-        *dst = NULL;
-
-        /* Substitute, free old one */
-        zfree(zgdbm_tied, sizeof(char *) * (old_len + 1));
-        zgdbm_tied = new_zgdbm_tied;
+static int
+is_tied(Param pm)
+{
+    if (pm->gsu.h == &gdbm_hash_gsu ) {
+        return 1;
     }
 
     return 0;
 }
-
-/*
- * 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;
-}
-
-/*
- * 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]=' ';
-    }
-}
+/* }}} */
 
 #else
 # error no gdbm
diff --git a/Src/Modules/db_gdbm.mdd b/Src/Modules/db_gdbm.mdd
index 210c221..4667222 100644
--- a/Src/Modules/db_gdbm.mdd
+++ b/Src/Modules/db_gdbm.mdd
@@ -7,6 +7,8 @@ fi
 '
 load=no
 
-autofeatures="b:ztie b:zuntie b:zgdbmpath p:zgdbm_tied"
+autofeatures="p:zgdbm_tied"
+
+moddeps="zsh/db"
 
 objects="db_gdbm.o"
diff --git a/Test/V11db_gdbm.ztst b/Test/V11db_gdbm.ztst
index 6d74cef..499108e 100644
--- a/Test/V11db_gdbm.ztst
+++ b/Test/V11db_gdbm.ztst
@@ -4,16 +4,21 @@
 
 %prep
 
- modname="zsh/db/gdbm"
+ module_path=( `pwd`/Modules )
+ modname1="zsh/db"
+ modname2="zsh/db/gdbm"
  dbfile=db.gdbm
- if ! zmodload $modname 2>/dev/null; then
-   ZTST_unimplemented="can't load $modname module for testing"
+ if ! zmodload $modname1 2>/dev/null; then
+   ZTST_unimplemented="can't load $modname1 module for testing"
+ fi
+ if ! zmodload $modname2 2>/dev/null; then
+   ZTST_unimplemented="can't load $modname2 module for testing"
  fi
  rm -f db.gdbm
 
 %test
 
- (zmodload -u $modname && zmodload $modname)
+ (zmodload -u $modname2 && zmodload $modname2)
 0:unload and reload the module without crashing
 
  ztie -d db/gdbm -f $dbfile dbase
@@ -304,23 +309,40 @@
 >漢字
 
  ztie -d db/gdbm -f $dbfile dbase
- zgdbmpath dbase
+ ztaddress dbase
  [[ $REPLY = */Test/db.gdbm ]] && echo correct
  zuntie dbase
  ztie -r -d db/gdbm -f $dbfile dbase
- zgdbmpath dbase
+ ztaddress dbase
  [[ $REPLY = */Test/db.gdbm ]] && echo correct
  zuntie -u dbase
-0:zgdbmpath builtin
+0:ztaddress builtin
 >correct
 >correct
 
  ztie -d db/gdbm -f $dbfile dbase
+ dbase[testkey]=value1
  fun() { while read line; do echo $line; done }
- eval "dbase[testkey]=value1" | fun
+ eval "dbase[testkey]=value2" | fun
+ echo $dbase[testkey]
+ ztclear dbase testkey
  echo $dbase[testkey]
 0:Test store in forked Zsh
 >value1
+>value2
+
+ ztie -d db/gdbm -f $dbfile -z dbase
+ dbase[testkey]=value1
+ fun() { while read line; do echo $line; done }
+ eval "dbase[testkey]=value2" | fun
+ echo $dbase[testkey]
+0:Test store in forked Zsh, with -z zero-cache option
+>value2
+
+ print -rl -- "${(okv@)zdb_backends}"
+0:Test zdb_backends
+>db/gdbm
+>loaded
 
 %clean
 

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

* Re: [PATCH] 2 modules, zsh/db, zsh/gdbm, bug-fixes
  2017-06-22  9:04 [PATCH] 2 modules, zsh/db, zsh/gdbm, bug-fixes Sebastian Gniazdowski
@ 2017-06-22  9:05 ` Sebastian Gniazdowski
  2017-06-23  9:29   ` Bart Schaefer
  2017-06-23  8:36 ` Bart Schaefer
  1 sibling, 1 reply; 15+ messages in thread
From: Sebastian Gniazdowski @ 2017-06-22  9:05 UTC (permalink / raw)
  To: zsh-workers

PS. moddeps in zsh/db/gdbm (.mdd file) seems to not work right.

-- 
Sebastian Gniazdowski
psprint /at/ zdharma.org


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

* Re: [PATCH] 2 modules, zsh/db, zsh/gdbm, bug-fixes
  2017-06-22  9:04 [PATCH] 2 modules, zsh/db, zsh/gdbm, bug-fixes Sebastian Gniazdowski
  2017-06-22  9:05 ` Sebastian Gniazdowski
@ 2017-06-23  8:36 ` Bart Schaefer
  2017-06-23 21:30   ` Daniel Shahaf
  2017-06-25  8:06   ` Sebastian Gniazdowski
  1 sibling, 2 replies; 15+ messages in thread
From: Bart Schaefer @ 2017-06-23  8:36 UTC (permalink / raw)
  To: zsh-workers

On Jun 22, 11:04am, Sebastian Gniazdowski wrote:
}
} 1. New module `zsh/db` that provides good architecture, via
} `*_main_entry` functions in client modules (zsh/db/gdbm in this
} patch).

Nit-pick: This does not include "the following two paragraphs" in any of
the licensing comments that refer to them, so technically it doesn't
pass the license requirements for borrowing from db_gdbm.c.

We might want to update a few of the error strings to make phrasing
and capitalization more similar to others in the shell, but there's
only minimal consistency there anyway so probably not urgent.

Given the bug-fixes in zsh/db/gdbm, this seems worth accepting if the
license comments are fixed.  Other opinions?


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

* Re: [PATCH] 2 modules, zsh/db, zsh/gdbm, bug-fixes
  2017-06-22  9:05 ` Sebastian Gniazdowski
@ 2017-06-23  9:29   ` Bart Schaefer
  0 siblings, 0 replies; 15+ messages in thread
From: Bart Schaefer @ 2017-06-23  9:29 UTC (permalink / raw)
  To: zsh-workers

On Jun 22, 11:05am, Sebastian Gniazdowski wrote:
}
} PS. moddeps in zsh/db/gdbm (.mdd file) seems to not work right.

Hmm.   I had gdbm marked as "static" in config.modules, and after
applying your patch:

srcdir='../../zsh-5.0/Src' CFMOD='../config.modules' \
  /bin/sh ../../zsh-5.0/Src/mkbltnmlst.sh bltinmods.list
ERROR: linked-in module `zsh/db/gdbm' depends on `zsh/db'
make[2]: *** [bltinmods.list] Error 1

So I made them both static and then recompiled, but there is still a
problem; if I don't explicitly load zsh/db first, loading zsh/db/gdbm
crashes.

If I explicitly do "zmodload -d zsh/db/gdbm zsh/db" then the crash is
also avoided.  It appears that mkbltnmlst.sh only creates add_dep()
calls for modules that are loaded into the shell by default (load=yes
in config.modules).  This only matters if there is a boot_ dependency
rather than merely a builtins dependency, so e.g. it doesn't break for
zsh/zftp and zsh/net/tcp.


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

* Re: [PATCH] 2 modules, zsh/db, zsh/gdbm, bug-fixes
  2017-06-23  8:36 ` Bart Schaefer
@ 2017-06-23 21:30   ` Daniel Shahaf
  2017-06-24  3:53     ` Bart Schaefer
  2017-06-25  8:06   ` Sebastian Gniazdowski
  1 sibling, 1 reply; 15+ messages in thread
From: Daniel Shahaf @ 2017-06-23 21:30 UTC (permalink / raw)
  To: zsh-workers

Bart Schaefer wrote on Fri, 23 Jun 2017 01:36 -0700:
> Given the bug-fixes in zsh/db/gdbm, this seems worth accepting if the
> license comments are fixed.  Other opinions?

There's no documentation.  That means I can't know what the patch _does_
unless I reverse engineer the implementation, which I'm ENOTIME for.
I assume the patch adds a zsh/db module that introduces abstractions
that all (existing and future) zsh/db/* modules can share?  +1 to the
concept, but again, I haven't reviewed the concrete details.

If (user or dev) documentation needs to be written, I would prefer it
were written _before_ the patch was accepted.

Is it a problem that the module name is a path-wise prefix of another
module's name (zsh/db v. zsh/db/gdbm).  Can't think of a particular
problem, but it's unprecedented.  [meant as a statement of fact]

There's a ton of vim folding added, {{{ }}}.  We don't have it
elsewhere, I assume it's just a scalpel.  Likewise with the
whitespace (indentation) changes.

The #if 0 looks alarming.  Maybe I missed discussion of it, but I'd be
surprised if the correct response to a bug in some configurations was to
disable the function call in all configurations, without even a version
number check (or, what would be better if it is possible, a runtime
probe for the bug).  The comment should link to the bug.  In closer
look, this change looks like a functionality change of zsh/db/gdbm
that's unrelated to the addition of zsh/db (and so should be a separate
patch).

db.c has variables declared in the middle of the block.  That's a
C99-ism, and while db/gdbm.c does it, the rest of zsh does not.  Not
critical.

db.c has a couple of zwarn() that don't state the origin of the error,
I think it'd be useful to make them zwarnnam(name="zsh/db") or so.

Cheers,

Daniel


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

* Re: [PATCH] 2 modules, zsh/db, zsh/gdbm, bug-fixes
  2017-06-23 21:30   ` Daniel Shahaf
@ 2017-06-24  3:53     ` Bart Schaefer
  2017-06-24  4:56       ` Sebastian Gniazdowski
  2017-06-24 17:36       ` Bart Schaefer
  0 siblings, 2 replies; 15+ messages in thread
From: Bart Schaefer @ 2017-06-24  3:53 UTC (permalink / raw)
  To: zsh-workers

On Jun 23,  9:30pm, Daniel Shahaf wrote:
}
} I assume the patch adds a zsh/db module that introduces abstractions
} that all (existing and future) zsh/db/* modules can share?

Yes, that was always the intent behind db_gdbm.c -- to be a model for
adding more databases later.

} If (user or dev) documentation needs to be written, I would prefer it
} were written _before_ the patch was accepted.

Agreed; Sebastian has taken the approach of writing extensive usage
messages in the commands themselves rather than editing yodl.  The
yodl doc should be updated.

} Is it a problem that the module name is a path-wise prefix of another
} module's name (zsh/db v. zsh/db/gdbm).

No, it is not an problem.  The whole business with slashes is just a
convention anyway, like using colons in zstyle contexts; the underlying
mechanism doesn't distinguish.

} There's a ton of vim folding added, {{{ }}}.  We don't have it
} elsewhere, I assume it's just a scalpel.  Likewise with the
} whitespace (indentation) changes.

Yes, I would also prefer the vim folding be removed, and the use of
CamelCapsVariableNames be made more like the rest of the code, but
both of those seemed unnecessarily picky.

} The #if 0 looks alarming.

It's around code that was never present in db_gdbm.c before, and protects
a function call that's "new" (in a historical sense) to the library.  I
think it's also nothing but an optimization, and therefore not essential.

} surprised if the correct response to a bug in some configurations

It's not a bug, it's a new(er) feature.

} db.c has variables declared in the middle of the block.  That's a
} C99-ism, and while db/gdbm.c does it, the rest of zsh does not.  Not
} critical.

There are also C++ / C99 style comments, which should be removed.  I
forgot to mention that in my first message.
 
} db.c has a couple of zwarn() that don't state the origin of the error,
} I think it'd be useful to make them zwarnnam(name="zsh/db") or so.

I've had a hard time deciding which of the warning functions is the
right one to use in a given situation.  zwarnnam() is usually meant to
prefix the message with the name of a command that caused the error;
it's not obvious that it should be used in a context where there is no
command involved.  (I haven't dug into whether that is the case for
the instances you've called out.)


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

* Re: [PATCH] 2 modules, zsh/db, zsh/gdbm, bug-fixes
  2017-06-24  3:53     ` Bart Schaefer
@ 2017-06-24  4:56       ` Sebastian Gniazdowski
  2017-06-25  0:50         ` Daniel Shahaf
  2017-06-24 17:36       ` Bart Schaefer
  1 sibling, 1 reply; 15+ messages in thread
From: Sebastian Gniazdowski @ 2017-06-24  4:56 UTC (permalink / raw)
  To: Bart Schaefer, zsh-workers

On 24 czerwca 2017 at 05:53:12, Bart Schaefer (schaefer@brasslantern.com) wrote:
> Yes, I would also prefer the vim folding be removed, and the use of
> CamelCapsVariableNames be made more like the rest of the code, but
> both of those seemed unnecessarily picky.

The folding helps in redis module, where set of GSU objects is devoted to 6 different types.

> } The #if 0 looks alarming.
> 
> It's around code that was never present in db_gdbm.c before, and protects
> a function call that's "new" (in a historical sense) to the library. I
> think it's also nothing but an optimization, and therefore not essential.

I've reported bug report to GDBM (before writing [PATCH] mail). Got response with correct address to send the report. The problem is: gdbm_reorganize doesn't work correctly on NFS filesystems. Short test code:

https://github.com/zdharma/hacking-private/tree/master/gdbm

Effect on NFS:

https://asciinema.org/a/ocjBIM9sEcvnNovA9AkStSwsQ

> I've had a hard time deciding which of the warning functions is the
> right one to use in a given situation. zwarnnam() is usually meant to
> prefix the message with the name of a command that caused the error;
> it's not obvious that it should be used in a context where there is no
> command involved. (I haven't dug into whether that is the case for
> the instances you've called out.)

Yes me too had hard time deciding on this. I can add some names, no problem.

-- 
Sebastian Gniazdowski
psprint /at/ zdharma.org


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

* Re: [PATCH] 2 modules, zsh/db, zsh/gdbm, bug-fixes
  2017-06-24  3:53     ` Bart Schaefer
  2017-06-24  4:56       ` Sebastian Gniazdowski
@ 2017-06-24 17:36       ` Bart Schaefer
  1 sibling, 0 replies; 15+ messages in thread
From: Bart Schaefer @ 2017-06-24 17:36 UTC (permalink / raw)
  To: zsh-workers

On Jun 23,  8:53pm, Bart Schaefer wrote:
}
} } The #if 0 looks alarming.
} 
} It's around code that was never present in db_gdbm.c before

I'm wrong about that, obviously.  It wasn't present in db_gdbm before
2015, but has been there the past 2 years.  However, given it's only
called when the resulting file should be empty, I'm unclear how the
NFS bug affects it.


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

* Re: [PATCH] 2 modules, zsh/db, zsh/gdbm, bug-fixes
  2017-06-24  4:56       ` Sebastian Gniazdowski
@ 2017-06-25  0:50         ` Daniel Shahaf
  2017-06-26  2:40           ` Sebastian Gniazdowski
  0 siblings, 1 reply; 15+ messages in thread
From: Daniel Shahaf @ 2017-06-25  0:50 UTC (permalink / raw)
  To: zsh-workers

Sebastian Gniazdowski wrote on Sat, 24 Jun 2017 06:56 +0200:
> On 24 czerwca 2017 at 05:53:12, Bart Schaefer (schaefer@brasslantern.com) wrote:
> > Yes, I would also prefer the vim folding be removed, and the use of
> > CamelCapsVariableNames be made more like the rest of the code, but
> > both of those seemed unnecessarily picky.

I don't think calling out the folding is picky.  It's perhaps a higher
degree of attention to detail than is usually seen on this list, but
it's a valid coding style issue.  (More on this below)

> The folding helps in redis module, where set of GSU objects is devoted to 6 different types.
> 

Two things.

Firstly, I'm not sure why you need the markers at all.  Vim's «:set
foldmarker=syntax» works well and doesn't require any markers.

Secondly, I have no objection to editor-supporting markup added,
provided that it is unobtrusive… which these markers are not.  They get
in the way of reading the code (they look like comments, and people who
read the code read comments).  They're liable to get out of date
(functions get split and renamed), and it won't scale to have every
$EDITOR's style of markers.  So I would rather they weren't added to the
VCS tree.  (but feel free to use them on your own machine, of course)

> > } The #if 0 looks alarming.
> > 
> > It's around code that was never present in db_gdbm.c before, and protects
> > a function call that's "new" (in a historical sense) to the library. I
> > think it's also nothing but an optimization, and therefore not essential.
> 
> [...] The problem is: gdbm_reorganize doesn't work correctly on NFS
> filesystems. Short test code:
> 
> https://github.com/zdharma/hacking-private/tree/master/gdbm
> 
> Effect on NFS:
> 
> https://asciinema.org/a/ocjBIM9sEcvnNovA9AkStSwsQ
> 

Isn't there a permanent link to a description of the bug?  Preferably in
the gdbm bug tracker, or at least on their mailing list?  That's needed
so people in the future can know why the workaround was added and judge
whether it can be removed.

(Both of these links are susceptible to rot: they may not work in a
few years)

> > I've had a hard time deciding which of the warning functions is the
> > right one to use in a given situation. zwarnnam() is usually meant to
> > prefix the message with the name of a command that caused the error;
> > it's not obvious that it should be used in a context where there is no
> > command involved. (I haven't dug into whether that is the case for
> > the instances you've called out.)
> 
> Yes me too had hard time deciding on this. I can add some names, no problem.

My reasoning was that every error/warning message should be signed by
its originator.  That's useful when the error is generated a few layers
away from the commmand the user actually invoked:
.
    % f() g
    % g() h
    % h() i
    % i() echo "j not found" >&2
    % f
    j not found
    %
.
where f, g, h are each a separate project.

If we don't know the name of the command or parameter/variable, then we
can at least sign with the name of the module.

Cheers,

Daniel


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

* Re: [PATCH] 2 modules, zsh/db, zsh/gdbm, bug-fixes
  2017-06-23  8:36 ` Bart Schaefer
  2017-06-23 21:30   ` Daniel Shahaf
@ 2017-06-25  8:06   ` Sebastian Gniazdowski
  2017-06-25 11:22     ` Sebastian Gniazdowski
  2017-06-25 22:52     ` Bart Schaefer
  1 sibling, 2 replies; 15+ messages in thread
From: Sebastian Gniazdowski @ 2017-06-25  8:06 UTC (permalink / raw)
  To: Bart Schaefer, zsh-workers

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

Sending state-of-art patch. Did multiple test runs on different machines (without obvious reason, the code didn't change), as always.

On 23.06.2017 at 10:36:43, Bart Schaefer (schaefer@brasslantern.com) wrote:
> Nit-pick: This does not include "the following two paragraphs" in any of
> the licensing comments that refer to them, so technically it doesn't
> pass the license requirements for borrowing from db_gdbm.c.

I've added the two paragraphs

> We might want to update a few of the error strings to make phrasing
> and capitalization more similar to others in the shell, but there's
> only minimal consistency there anyway so probably not urgent.

I've changed messages to start lowercase

Wasn't sure what to do with the NFS bug. I did this:

    if (!ht || ht->hsize == 0) {
        (void)gdbm_reorganize(dbf);
    }

So it reorganizes if clearing hash with =().

Tried compiling with "-ansi" on three machines, it was OK (except for C++ comments), but I've changed the middle-block declarations, to be C89.

As for folding, I attach Emacs screenshot, to show it really helps. Learned the folds from reading NERDTree source (vim plugin), it was huge, but had the folds and they helped.

--  
Sebastian Gniazdowski
psprint /at/ zdharma.org

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

commit 84b498bcfd9a3db0d30c554bab5c005406fc2367
Author: Sebastian Gniazdowski <sgniazdowski@gmail.com>
Date:   Sun Jun 25 08:52:45 2017 +0200

    Two modules

diff --git a/Src/Modules/db.c b/Src/Modules/db.c
new file mode 100644
index 0000000..839ae22
--- /dev/null
+++ b/Src/Modules/db.c
@@ -0,0 +1,811 @@
+/* -*- Mode: C; c-default-style: "linux"; c-basic-offset: 4; indent-tabs-mode: nil -*-
+ * vim:sw=4:sts=4:et
+ */
+
+/*
+ * db.c - general database module, forwards to backends
+ *
+ * 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.
+ *
+ * In no event shall Sebastian Gniazdowski or the Zsh Development Group be
+ * liable to any party for direct, indirect, special, incidental, or
+ * consequential damages arising out of the use of this software and its
+ * documentation, even if Paul Falstad and the Zsh Development Group have
+ * been advised of the possibility of such damage.
+ *
+ * Sebastian Gniazdowski and the Zsh Development Group specifically disclaim
+ * any warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software provided
+ * hereunder is on an "as is" basis, and Paul Falstad and the Zsh Development
+ * Group have no obligation to provide maintenance, support, updates,
+ * enhancements, or modifications.
+ */
+
+#include "db.mdh"
+#include "db.pro"
+#include "db.h"
+
+/* MACROS {{{ */
+#ifndef PM_UPTODATE
+#define PM_UPTODATE     (1<<19) /* Parameter has up-to-date data (e.g. loaded from DB) */
+#endif
+/* }}} */
+/* DECLARATIONS {{{ */
+static void ztie_usage();
+static void zuntie_usage();
+static void ztaddress_usage();
+static void ztclear_usage();
+
+static HashTable createhashtable(char *name);
+static void freebackendnode(HashNode hn);
+static void backend_scan_fun(HashNode hn, int unused);
+
+static Param createhashparam(char *name, int flags);
+
+/* Type of provided (by backend module) entry-point */
+typedef int (*DbBackendEntryPoint)(VA_ALIST1(int cmd));
+
+struct backend_node {
+    struct hashnode node;
+    DbBackendEntryPoint main_entry;
+};
+
+typedef struct backend_node *BackendNode;
+
+/* Maps db/dbtype onto BackendNode */
+static HashTable backends_hash = NULL;
+
+/* For searching with scanhashtable */
+char *In_ParamName = NULL;
+DbBackendEntryPoint Out_FoundBe = NULL;
+/* }}} */
+/* ARRAY: builtin {{{ */
+static struct builtin bintab[] =
+{
+    /* h - help, d - backend type, r - read-only, a/f - address/file,
+     * l - load password from terminal, p - password as argument,
+     * P - password from file, z - zero read-cache */
+    BUILTIN("ztie", 0, bin_ztie, 0, -1, 0, "hrzf:d:a:p:P:L:", NULL),
+    BUILTIN("zuntie", 0, bin_zuntie, 0, -1, 0, "uh", NULL),
+    BUILTIN("ztaddress", 0, bin_ztaddress, 0, -1, 0, "h", NULL),
+    BUILTIN("ztclear", 0, bin_ztclear, 0, -1, 0, "h", NULL),
+};
+/* }}} */
+/* ARRAY: other {{{ */
+#define ROARRPARAMDEF(name, var)                                        \
+    { name, PM_ARRAY | PM_READONLY, (void *) var, NULL,  NULL, NULL, NULL }
+/* }}} */
+
+/* FUNCTION: update_user_hash {{{ */
+static void update_user_hash(char *id, int is_loading) {
+    Param pm = (Param) paramtab->getnode(paramtab, "zdb_backends");
+    HashTable ht;
+    HashNode hn;
+    Param val_pm;
+
+    if(!pm) {
+        zwarn("no such parameter: zdb_backends, internal error");
+        return;
+    }
+
+    /* Must be a special hash */
+    if (!(pm->node.flags & PM_HASHED) || !(pm->node.flags & PM_SPECIAL)) {
+        zwarn("parameter zdb_backends is defined by user, will not update it");
+        return;
+    }
+
+    ht = pm->u.hash;
+    hn = gethashnode2(ht, id);
+    val_pm = (Param) hn;
+
+    if (val_pm) {
+        if (val_pm->u.str) {
+            zsfree(val_pm->u.str);
+            val_pm->u.str = NULL;
+        }
+        if (is_loading) {
+            val_pm->u.str = ztrdup("re-loaded");
+        } else {
+            val_pm->u.str = ztrdup("unloaded");
+        }
+    } else {
+        val_pm = (Param) zshcalloc(sizeof (*val_pm));
+        val_pm->node.flags = PM_SCALAR | PM_HASHELEM;
+        val_pm->gsu.s = &stdscalar_gsu;
+        if (is_loading) {
+            val_pm->u.str = ztrdup("loaded");
+        } else {
+            val_pm->u.str = ztrdup("unloaded");
+        }
+        addhashnode(ht, ztrdup(id), val_pm); /* sets pm->node.nam */
+    }
+}
+/* }}} */
+/* FUNCTION: zsh_db_register_backend {{{ */
+
+/**/
+void
+zsh_db_register_backend(char *id, void *entry_point) {
+    BackendNode bn = (BackendNode)zshcalloc(sizeof(struct backend_node));
+    if (bn) {
+        bn->main_entry = entry_point;
+        addhashnode(backends_hash, ztrdup(id), (void *)bn);
+    } else {
+        zwarn("out of memory when allocating backend entry");
+        return;
+    }
+
+    update_user_hash(id, 1);
+}
+/* }}} */
+/* FUNCTION: zsh_db_unregister_backend {{{ */
+
+/**/
+void
+zsh_db_unregister_backend(char *id) {
+    HashNode bn = backends_hash->removenode(backends_hash, id);
+    if (bn) {
+        freebackendnode(bn);
+    }
+    update_user_hash(id, 0);
+}
+/* }}} */
+/* FUNCTION: bin_ztie {{{ */
+
+/**/
+static int
+bin_ztie(char *nam, char **args, Options ops, UNUSED(int func))
+{
+    char *pmname;
+    char *address = NULL, *pass = NULL, *pfile = NULL, *lazy = NULL;
+    int rdonly = 0, zcache = 0, pprompt = 0;
+
+    /* Check options */
+
+    if (OPT_ISSET(ops,'h')) {
+        ztie_usage();
+        return 0;
+    }
+
+    if (!OPT_ISSET(ops,'d')) {
+        zwarnnam(nam, "you must pass e.g. `-d db/redis', see `-h'");
+        return 1;
+    }
+
+    if (!OPT_ISSET(ops,'f') && !OPT_ISSET(ops,'a')) {
+        zwarnnam(nam, "you must pass `-f' (file, or `-a', address) with e.g. {host}[:port][/[db_idx][/key]], see `-h'");
+        return 1;
+    }
+
+    if (0 != strcmp(OPT_ARG(ops, 'd'), "db/redis") && 0 != strcmp(OPT_ARG(ops, 'd'), "db/gdbm") ) {
+        zwarnnam(nam, "Unsupported backend type `%s', see `-h'", OPT_ARG(ops, 'd'));
+        return 1;
+    }
+
+    /* Check argument */
+    pmname = *args;
+
+    if (!pmname) {
+        zwarnnam(nam, "You must pass non-option argument - the target parameter to create, see -h");
+        return 1;
+    }
+
+    /* Address */
+    if (OPT_ISSET(ops,'f')) {
+        address = OPT_ARG(ops,'f');
+    } else {
+        address = OPT_ARG(ops,'a');
+    }
+
+    /* Read-only */
+    if (OPT_ISSET(ops,'r')) {
+        rdonly = 1;
+    } else {
+        rdonly = 0;
+    }
+
+    /* Zero-cache */
+    if (OPT_ISSET(ops,'z')) {
+        zcache = 1;
+    } else {
+        zcache = 0;
+    }
+
+    /* Password */
+    if (OPT_ISSET(ops,'p')) {
+        pass = OPT_ARG(ops,'p');
+    }
+
+    /* Password file */
+    if (OPT_ISSET(ops,'P')) {
+        pfile = OPT_ARG(ops,'P');
+    }
+
+    /* Password load request */
+    if (OPT_ISSET(ops,'l')) {
+        pprompt = 1;
+    } else {
+        pprompt = 0;
+    }
+
+    /* Lazy binding */
+    if (OPT_ISSET(ops,'L')) {
+        lazy = OPT_ARG(ops,'L');
+    }
+
+    BackendNode node = NULL;
+    DbBackendEntryPoint be = NULL;
+
+    if(!(node = (BackendNode) gethashnode2(backends_hash, OPT_ARG(ops, 'd')))) {
+        zwarnnam(nam, "backend module for %s not loaded (or loaded before the main `db' module)", OPT_ARG(ops, 'd'));
+        return 1;
+    }
+
+    be = node->main_entry;
+    if (!be) {
+        zwarnnam(nam, "backend for %s is uninitialized", OPT_ARG(ops, 'd'));
+        return 1;
+    }
+
+    return be(DB_TIE, address, rdonly, zcache, pass, pfile, pprompt, pmname, lazy);
+}
+/* }}} */
+/* FUNCTION: bin_zuntie {{{ */
+
+/**/
+static int
+bin_zuntie(char *nam, char **args, Options ops, UNUSED(int func))
+{
+    char *pmname;
+    int ret = 0, rountie = 0;
+
+    if (OPT_ISSET(ops,'h')) {
+        zuntie_usage();
+        return 0;
+    }
+
+    if (!*args) {
+        zwarnnam(nam, "at least one variable name is needed, see -h");
+        return 1;
+    }
+
+    for (pmname = *args; *args++; pmname = *args) {
+        In_ParamName = pmname;
+        Out_FoundBe = NULL;
+
+        scanhashtable(backends_hash, 0, 0, 0, backend_scan_fun, 0);
+
+        if (!Out_FoundBe) {
+            zwarnnam(nam, "didn't recognize `%s' as a tied parameter", pmname);
+            continue;
+        }
+
+        if (OPT_ISSET(ops,'u')) {
+            rountie = 1;
+        }
+
+        ret = Out_FoundBe(DB_UNTIE, rountie, pmname) ? 1 : ret;
+    }
+
+    return ret;
+}
+/* }}} */
+/* FUNCTION: bin_ztaddress {{{ */
+
+/**/
+static int
+bin_ztaddress(char *nam, char **args, Options ops, UNUSED(int func))
+{
+    char *pmname;
+    int ret = 0;
+
+    if (OPT_ISSET(ops,'h')) {
+        ztaddress_usage();
+        return 0;
+    }
+
+    if (!*args) {
+        zwarnnam(nam, "one parameter name is needed, see -h");
+        return 1;
+    }
+
+    pmname = *args;
+    In_ParamName = pmname;
+    Out_FoundBe = NULL;
+
+    scanhashtable(backends_hash, 0, 0, 0, backend_scan_fun, 0);
+
+    if (!Out_FoundBe) {
+        zwarnnam(nam, "didn't recognize `%s' as a tied parameter", pmname);
+        return 1;
+    }
+
+    ret = Out_FoundBe(DB_GET_ADDRESS, pmname) ? 1 : ret;
+
+    return ret;
+}
+/* }}} */
+/* FUNCTION: bin_ztclear {{{ */
+
+/**/
+static int
+bin_ztclear(char *nam, char **args, Options ops, UNUSED(int func))
+{
+    char *pmname, *key;
+    int ret = 0;
+
+    if (OPT_ISSET(ops,'h')) {
+        ztclear_usage();
+        return 0;
+    }
+
+    if (!*args) {
+        zwarnnam(nam, "one-to-two parameters' names are needed, see -h");
+        return 1;
+    }
+
+    pmname = *args;
+    key = *(args+1);
+
+    In_ParamName = pmname;
+    Out_FoundBe = NULL;
+
+    scanhashtable(backends_hash, 0, 0, 0, backend_scan_fun, 0);
+
+    if (!Out_FoundBe) {
+        zwarnnam(nam, "didn't recognize `%s' as a tied parameter", pmname);
+        return 1;
+    }
+
+    ret = Out_FoundBe(DB_CLEAR_CACHE, pmname, key) ? 1 : ret;
+
+    return ret;
+}
+/* }}} */
+
+/*************** MAIN CODE ***************/
+
+/* ARRAY features {{{ */
+static struct features module_features =
+{
+    bintab, sizeof(bintab)/sizeof(*bintab),
+    NULL, 0,
+    NULL, 0,
+    NULL, 0,
+    0
+};
+/* }}} */
+
+/* FUNCTION: setup_ {{{ */
+
+/**/
+int
+setup_(UNUSED(Module m))
+{
+    Param pm  = NULL;
+
+    /* Create private registration hash */
+    if (!(backends_hash = createhashtable("ZSH_BACKENDS"))) {
+        zwarn("cannot create backend-register hash");
+        return 1;
+    }
+
+    /* Unset zdb_backends if it exists. Inherited from db_gdbm,
+     * it clears current scope, leaving any upper scope untouched,
+     * which can result in zdb_backends being local. Can be seen
+     * as a feature */
+    if ((pm = (Param)paramtab->getnode(paramtab, "zdb_backends")) && !(pm->node.flags & PM_UNSET)) {
+        pm->node.flags &= ~PM_READONLY;
+        if (unsetparam_pm(pm, 0, 1)) {
+            zwarn("cannot properly manage scoping of variables");
+            return 1;
+        }
+    }
+
+    /* Create zdb_backends public hash that will hold state of backends */
+    pm = createhashparam("zdb_backends", PM_READONLY | PM_REMOVABLE);
+    if (NULL == pm) {
+        zwarn("cannot create user backends-list hash parameter");
+        if (backends_hash) {
+            deletehashtable(backends_hash);
+            backends_hash = NULL;
+        }
+        return 1;
+    }
+    pm->gsu.h = &stdhash_gsu;
+
+    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))
+{
+    return 0;
+}
+/* }}} */
+/* FUNCTION: cleanup_ {{{ */
+
+/**/
+int
+cleanup_(Module m)
+{
+    /* This frees `zredis_tied` */
+    return setfeatureenables(m, &module_features, NULL);
+
+    if (backends_hash) {
+        deletehashtable(backends_hash);
+        backends_hash = NULL;
+    }
+}
+/* }}} */
+/* FUNCTION: finish_ {{{ */
+
+/**/
+int
+finish_(UNUSED(Module m))
+{
+    return 0;
+}
+/* }}} */
+
+/*************** UTILITIES ***************/
+
+/* FUNCTION: createhashtable {{{ */
+static HashTable
+createhashtable(char *name)
+{
+    HashTable ht;
+
+    ht = newhashtable(8, name, NULL);
+
+    ht->hash        = hasher;
+    ht->emptytable  = emptyhashtable;
+    ht->filltable   = NULL;
+    ht->cmpnodes    = strcmp;
+    ht->addnode     = addhashnode;
+    ht->getnode     = gethashnode2;
+    ht->getnode2    = gethashnode2;
+    ht->removenode  = removehashnode;
+    ht->disablenode = NULL;
+    ht->enablenode  = NULL;
+    ht->freenode    = freebackendnode;
+    ht->printnode   = NULL;
+
+    return ht;
+}
+/* }}} */
+/* FUNCTION: createhashparam {{{ */
+static Param
+createhashparam(char *name, int flags)
+{
+    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(7, name);
+    if (!pm->u.hash) {
+        paramtab->removenode(paramtab, name);
+        paramtab->freenode(&pm->node);
+        zwarnnam(name, "out of memory when allocating user-visible hash of backends");
+        return NULL;
+    }
+
+    /* Does free Param (unsetfn is called) */
+    ht->freenode = zsh_db_freeparamnode;
+
+    return pm;
+}
+/* }}} */
+/* FUNCTION: freebackendnode {{{ */
+static void
+freebackendnode(HashNode hn)
+{
+    zsfree(hn->nam);
+    zfree(hn, sizeof(struct backend_node));
+}
+/* }}} */
+/* FUNCTION: backend_scan_fun {{{ */
+static void
+backend_scan_fun(HashNode hn, int unused)
+{
+    BackendNode bn = (BackendNode)hn;
+    DbBackendEntryPoint be = bn->main_entry;
+    if (!be) {
+        zwarn("backend %s registered but uninitialized", hn->nam);
+        return;
+    }
+    /* 0 - shell true value */
+    if(0 == be(DB_IS_TIED, In_ParamName)) {
+        Out_FoundBe = be;
+    }
+}
+/* }}} */
+
+/*********** SHARED UTILITIES ***********/
+
+/* FUNCTION: zsh_db_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.
+ */
+
+/**/
+char *
+zsh_db_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: zsh_db_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 */
+
+/**/
+void
+zsh_db_set_length(char *buf, int size)
+{
+    buf[size]='\0';
+    while (-- size >= 0) {
+        buf[size]=' ';
+    }
+}
+/* }}} */
+/* FUNCTION: zsh_db_standarize_hash {{{ */
+
+/**/
+void
+zsh_db_standarize_hash(Param pm) {
+    if (0 == (pm->node.flags & PM_HASHED)) {
+        return;
+    }
+
+    pm->node.flags &= ~(PM_SPECIAL|PM_READONLY);
+    pm->gsu.h = &stdhash_gsu;
+
+    HashTable ht = pm->u.hash;
+
+    ht->hash        = hasher;
+    ht->emptytable  = emptyhashtable;
+    ht->filltable   = NULL;
+    ht->cmpnodes    = strcmp;
+    ht->addnode     = addhashnode;
+    ht->getnode     = gethashnode;
+    ht->getnode2    = gethashnode2;
+    ht->removenode  = removehashnode;
+    ht->disablenode = NULL;
+    ht->enablenode  = NULL;
+    ht->freenode    = zsh_db_freeparamnode;
+}
+/* }}} */
+/* FUNCTION: zsh_db_arr_append {{{ */
+
+/*
+ * Adds parameter name (to given `*_tied` array)
+ */
+
+/**/
+int
+zsh_db_arr_append(char ***arr, const char *input_s)
+{
+    int old_len = arrlen(*arr);
+    char **new_arr = zshcalloc( (old_len+2) * sizeof(char *));
+
+    /* Copy */
+    char **p = *arr;
+    char **dst = new_arr;
+    while (*p) {
+        *dst++ = *p++;
+    }
+
+    /* Append new one */
+    *dst = ztrdup(input_s);
+
+    /* Substitute, free old one */
+    zfree(*arr, sizeof(char *) * (old_len + 1));
+    *arr = new_arr;
+
+    return 0;
+}
+/* }}} */
+/* FUNCTION: zsh_db_filter_arr {{{ */
+
+/*
+ * Removes parameter name (from given `*_tied` array)
+ */
+
+/**/
+int
+zsh_db_filter_arr(char ***arr, const char *input_s)
+{
+    int old_len = arrlen(*arr), new_len;
+    char **new_arr, **dst;
+
+    /* 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 = *arr;
+    while (*p) {
+        if (0==strcmp(input_s,*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 */
+    new_len = arrlen(*arr);
+    if (new_len != old_len) {
+        new_arr = (char **) zshcalloc((new_len+1) * sizeof(char *));
+
+        /* Copy */
+        p = *arr;
+        dst = new_arr;
+        while (*p) {
+            *dst++ = *p++;
+        }
+        *dst = NULL;
+
+        /* Substitute, free old one */
+        zfree(*arr, sizeof(char *) * (old_len + 1));
+        *arr = new_arr;
+    }
+
+    return 0;
+}
+/* }}} */
+/* FUNCTION: zsh_db_freeparamnode {{{ */
+
+/**/
+void
+zsh_db_freeparamnode(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));
+}
+/* }}} */
+
+/***************** USAGE *****************/
+
+/* FUNCTION: ztie_usage {{{ */
+
+static void
+ztie_usage()
+{
+    fprintf(stdout, "Usage: ztie -d db/... [-z] [-r] [-p password] [-P password_file] [-L type]"
+            "-f/-a {db_address} {parameter_name}\n");
+    fprintf(stdout, "Options:\n");
+    fprintf(stdout, " -d:       select database type: \"db/gdbm\", \"db/redis\"\n");
+    fprintf(stdout, " -z:       zero-cache for read operations (always access database)\n");
+    fprintf(stdout, " -r:       create read-only parameter\n" );
+    fprintf(stdout, " -f or -a: database-address in format {host}[:port][/[db_idx][/key]] or a file path\n");
+    fprintf(stdout, " -p:       database-password to be used for authentication\n");
+    fprintf(stdout, " -P:       path to file with database-password\n");
+    fprintf(stdout, " -L:       lazy binding - provide type of key to create if it doesn't exist "
+                    "(string, set, zset, hash, list)\n");
+    fprintf(stdout, "The {parameter_name} - choose name for the created database-bound parameter\n");
+    fflush(stdout);
+}
+/* }}} */
+/* FUNCTION: zuntie_usage {{{ */
+
+static void
+zuntie_usage()
+{
+    fprintf(stdout, "Usage: zuntie [-u] {tied-variable-name} [tied-variable-name] ...\n");
+    fprintf(stdout, "Options:\n");
+    fprintf(stdout, " -u: Allow to untie read-only parameter\n");
+    fprintf(stdout, "Description: detaches variable from its database and removes the variable;\n");
+    fprintf(stdout, "             database is not cleared (unlike when unset)\n");
+    fflush(stdout);
+}
+/* }}} */
+/* FUNCTION: ztaddress_usage {{{ */
+
+static void
+ztaddress_usage()
+{
+    fprintf(stdout, "Usage: ztaddress {tied-parameter-name}\n");
+    fprintf(stdout, "Description: stores address used by given parameter to $REPLY\n");
+    fflush(stdout);
+}
+/* }}} */
+/* FUNCTION: ztclear_usage {{{ */
+
+static void
+ztclear_usage()
+{
+    fprintf(stdout, "Usage: ztclear {tied-parameter-name} [key name]\n");
+    fprintf(stdout, "Description: clears cache of given hash/key or of given plain\n");
+    fprintf(stdout, "             parameter: set (array), list (array), string (scalar);\n");
+    fprintf(stdout, "             pass `-z' to ztie to globally disable cache for parameter\n");
+    fflush(stdout);
+}
+/* }}} */
+
diff --git a/Src/Modules/db.h b/Src/Modules/db.h
new file mode 100644
index 0000000..321e5b3
--- /dev/null
+++ b/Src/Modules/db.h
@@ -0,0 +1,42 @@
+/*
+ * db.h
+ *
+ * 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.
+ *
+ * In no event shall Sebastian Gniazdowski or the Zsh Development Group be
+ * liable to any party for direct, indirect, special, incidental, or
+ * consequential damages arising out of the use of this software and its
+ * documentation, even if Paul Falstad and the Zsh Development Group have
+ * been advised of the possibility of such damage.
+ *
+ * Sebastian Gniazdowski and the Zsh Development Group specifically disclaim
+ * any warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software provided
+ * hereunder is on an "as is" basis, and Paul Falstad and the Zsh Development
+ * Group have no obligation to provide maintenance, support, updates,
+ * enhancements, or modifications.
+ */
+
+/* Backend commands */
+#define DB_TIE 1
+#define DB_UNTIE 2
+#define DB_IS_TIED 3
+#define DB_GET_ADDRESS 4
+#define DB_CLEAR_CACHE 5
+
+/* Data types, created according to redis */
+#define DB_KEY_TYPE_NONE 0  /* unknown type because non-existent key */
+#define DB_KEY_TYPE_UNKNOWN 1
+#define DB_KEY_TYPE_NO_KEY 2
+#define DB_KEY_TYPE_STRING 3
+#define DB_KEY_TYPE_LIST 4
+#define DB_KEY_TYPE_SET 5
+#define DB_KEY_TYPE_ZSET 6
+#define DB_KEY_TYPE_HASH 7
diff --git a/Src/Modules/db.mdd b/Src/Modules/db.mdd
new file mode 100644
index 0000000..73ac83d
--- /dev/null
+++ b/Src/Modules/db.mdd
@@ -0,0 +1,7 @@
+name=zsh/db
+link=dynamic
+load=no
+
+autofeatures="b:ztie b:zuntie b:ztaddress b:ztclear"
+
+objects="db.o"
diff --git a/Src/Modules/db_gdbm.c b/Src/Modules/db_gdbm.c
index 0ab0fe7..2e6d023 100644
--- a/Src/Modules/db_gdbm.c
+++ b/Src/Modules/db_gdbm.c
@@ -1,3 +1,7 @@
+/* -*- Mode: C; c-default-style: "linux"; c-basic-offset: 4; indent-tabs-mode: nil -*-
+ * vim:sw=4:sts=4:et
+ */
+
 /*
  * db_gdbm.c - bindings for gdbm
  *
@@ -15,13 +19,13 @@
  * purpose, provided that the above copyright notice and the following
  * two paragraphs appear in all copies of this software.
  *
- * In no event shall Clint Adams or the Zsh Development
+ * In no event shall Clint Adams, Sebastian Gniazdowski or the Zsh Development
  * Group be liable to any party for direct, indirect, special, incidental, or
  * consequential damages arising out of the use of this software and its
  * documentation, even if Peter Stephenson, Sven Wischnowsky and the Zsh
  * Development Group have been advised of the possibility of such damage.
  *
- * Clint Adams and the Zsh Development Group
+ * Clint Adams, Sebastian Gniazdowski, and the Zsh Development Group
  * specifically disclaim any warranties, including, but not limited to, the
  * implied warranties of merchantability and fitness for a particular purpose.
  * The software provided hereunder is on an "as is" basis, and Peter
@@ -33,16 +37,21 @@
 
 #include "db_gdbm.mdh"
 #include "db_gdbm.pro"
+#include "db.epro"
+#include "db.h"
 
+/* MACROS {{{ */
 #ifndef PM_UPTODATE
 #define PM_UPTODATE     (1<<19) /* Parameter has up-to-date data (e.g. loaded from DB) */
 #endif
-
+/* }}} */
+/* DECLARATIONS {{{ */
 static Param createhash( char *name, int flags );
-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 int is_tied_cmd(char *pmname);
+static int is_tied(Param pm);
+
+static int no_database_action = 0;
+/* }}} */
 
 /*
  * Make sure we have all the bits I'm using for memory mapping, otherwise
@@ -52,7 +61,7 @@ static void set_length(char *buf, int size);
 
 #include <gdbm.h>
 
-static char *backtype = "db/gdbm";
+/* ARRAY: GSU {{{ */
 
 /*
  * Longer GSU structure, to carry GDBM_FILE of owning
@@ -72,25 +81,26 @@ static char *backtype = "db/gdbm";
  */
 
 struct gsu_scalar_ext {
-    struct gsu_scalar std;
-    GDBM_FILE dbf;
+    struct gsu_scalar std; /* Size of three pointers */
+    int type;
+    int use_cache;
+    int is_lazy;
     char *dbfile_path;
+    char *key;
+    size_t key_len;
+    char *password;
+    int fdesc;
+    GDBM_FILE dbf; /* a pointer */
 };
 
 /* Source structure - will be copied to allocated one,
  * with `dbf` filled. `dbf` allocation <-> gsu allocation. */
 static const struct gsu_scalar_ext gdbm_gsu_ext =
-{ { gdbmgetfn, gdbmsetfn, gdbmunsetfn }, 0, 0 };
+    { { gdbmgetfn, gdbmsetfn, gdbmunsetfn }, 0, 0 };
 
 /**/
 static const struct gsu_hash gdbm_hash_gsu =
-{ hashgetfn, gdbmhashsetfn, gdbmhashunsetfn };
-
-static struct builtin bintab[] = {
-    BUILTIN("ztie", 0, bin_ztie, 1, -1, 0, "d:f:r", NULL),
-    BUILTIN("zuntie", 0, bin_zuntie, 1, -1, 0, "u", NULL),
-    BUILTIN("zgdbmpath", 0, bin_zgdbmpath, 1, -1, 0, "", NULL),
-};
+    { hashgetfn, gdbmhashsetfn, gdbmhashunsetfn };
 
 #define ROARRPARAMDEF(name, var) \
     { name, PM_ARRAY | PM_READONLY, (void *) var, NULL,  NULL, NULL, NULL }
@@ -98,157 +108,242 @@ static struct builtin bintab[] = {
 /* Holds names of all tied parameters */
 char **zgdbm_tied;
 
-static struct paramdef patab[] = {
-    ROARRPARAMDEF( "zgdbm_tied", &zgdbm_tied ),
-};
+static struct paramdef patab[] =
+    { ROARRPARAMDEF( "zgdbm_tied", &zgdbm_tied ), };
+/* }}} */
+
+/* FUNCTION: gdbm_main_entry {{{ */
+static int
+gdbm_main_entry(VA_ALIST1(int cmd))
+    VA_DCL
+{
+    char *address = NULL, *pass = NULL, *pfile = NULL, *pmname = NULL, *key = NULL, *lazy = NULL;
+    int rdonly = 0, zcache = 0, pprompt = 0, rountie = 0;
+    char *argv[2];
+
+    va_list ap;
+    VA_DEF_ARG(int cmd);
+
+    VA_START(ap, cmd);
+    VA_GET_ARG(ap, cmd, int);
+
+    switch (cmd) {
+    case DB_TIE:
+        /* Order is:
+         * -a/f address, char *
+         * -r read-only, int
+         * -z zero-cache, int
+         * -p password, char *
+         * -P file with password, char *
+         * -l prompt for password, int
+         * -L lazy binding type, char *
+         * parameter name, char *
+         */
+        address = va_arg(ap, char *);
+        rdonly = va_arg(ap, int);
+        zcache = va_arg(ap, int);
+        pass = va_arg(ap, char *);
+        pfile = va_arg(ap, char *);
+        pprompt = va_arg(ap, int);
+        pmname = va_arg(ap, char *);
+        lazy = va_arg(ap, char *);
+        return zgtie_cmd(address, rdonly, zcache, pass, pfile, pprompt, pmname, lazy);
+
+    case DB_UNTIE:
+        /* Order is:
+         * -u untie read only parameter, int
+         * parameter name, char *
+         */
+        rountie = va_arg(ap, int);
+        pmname = va_arg(ap, char *);
+        argv[0] = pmname;
+        argv[1] = NULL;
+        return zguntie_cmd(rountie, argv);
+
+    case DB_IS_TIED:
+        /* Order is:
+         * parameter name, char *
+         */
+        pmname = va_arg(ap, char *);
+        return is_tied_cmd(pmname);
+
+    case DB_GET_ADDRESS:
+        /* Order is:
+         * Parameter name, char *
+         */
+        pmname = va_arg(ap, char*);
+        return gdbmpath_cmd(pmname);
+
+    case DB_CLEAR_CACHE:
+        /* Order is:
+         * Parameter name, char *
+         * key name, char *
+         */
+        pmname = va_arg(ap, char*);
+        key = va_arg(ap, char*);
+        return gdbmclear_cmd(pmname, key);
+
+    default:
+#ifdef DEBUG
+        dputs("Bad command %d in redis_main_entry", cmd);
+#endif
+        break;
+    }
+    return 1;
+}
+/* }}} */
+/* FUNCTION: zgtie_cmd {{{ */
 
 /**/
 static int
-bin_ztie(char *nam, char **args, Options ops, UNUSED(int func))
+zgtie_cmd(char *address, int rdonly, int zcache, char *pass, char *pfile, int pprompt, char *pmname, char *lazy)
 {
-    char *resource_name, *pmname;
     GDBM_FILE dbf = NULL;
     int read_write = GDBM_SYNC, pmflags = PM_REMOVABLE;
     Param tied_param;
+    struct gsu_scalar_ext *dbf_carrier;
 
-    if(!OPT_ISSET(ops,'d')) {
-        zwarnnam(nam, "you must pass `-d %s'", backtype);
-	return 1;
-    }
-    if(!OPT_ISSET(ops,'f')) {
-        zwarnnam(nam, "you must pass `-f' with a filename", NULL);
-	return 1;
-    }
-    if (OPT_ISSET(ops,'r')) {
-	read_write |= GDBM_READER;
-	pmflags |= PM_READONLY;
-    } else {
-	read_write |= GDBM_WRCREAT;
+    if (!address) {
+        zwarn("you must pass `-f' or '-a' path to the database", NULL);
+        return 1;
     }
 
-    /* Here should be a lookup of the backend type against
-     * a registry, if generam DB mechanism is to be added */
-    if (strcmp(OPT_ARG(ops, 'd'), backtype) != 0) {
-        zwarnnam(nam, "unsupported backend type `%s'", OPT_ARG(ops, 'd'));
-	return 1;
+    if (!pmname) {
+        zwarn("you must pass non-option argument - the target parameter to create, see -h");
+        return 1;
     }
 
-    resource_name = OPT_ARG(ops, 'f');
-    pmname = *args;
+    if (rdonly) {
+        read_write |= GDBM_READER;
+        pmflags |= PM_READONLY;
+    } else {
+        read_write |= GDBM_WRCREAT;
+    }
 
     if ((tied_param = (Param)paramtab->getnode(paramtab, pmname)) &&
-	!(tied_param->node.flags & PM_UNSET)) {
-	/*
-	 * 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
+        !(tied_param->node.flags & PM_UNSET)) {
+        /*
+         * 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;
-    }
-
-    gdbm_errno=0;
-    dbf = gdbm_open(resource_name, 0, read_write, 0666, 0);
+         *
+         * 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;
+    }
+
+    gdbm_errno = 0;
+    dbf = gdbm_open(address, 0, read_write, 0666, 0);
     if(dbf == NULL) {
-	zwarnnam(nam, "error opening database file %s (%s)", resource_name, gdbm_strerror(gdbm_errno));
-	return 1;
+        zwarn("error opening database file %s (%s)", address, gdbm_strerror(gdbm_errno));
+        return 1;
     }
 
     if (!(tied_param = createhash(pmname, pmflags))) {
-        zwarnnam(nam, "cannot create the requested parameter %s", pmname);
-	fdtable[gdbm_fdesc(dbf)] = FDT_UNUSED;
-	gdbm_close(dbf);
-	return 1;
+        zwarn("cannot create the requested parameter %s", pmname);
+        gdbm_close(dbf);
+        return 1;
     }
 
-    addmodulefd(gdbm_fdesc(dbf), FDT_MODULE);
-    append_tied_name(pmname);
-
     tied_param->gsu.h = &gdbm_hash_gsu;
 
-    /* Allocate parameter sub-gsu, fill dbf field. 
+    /* Allocate parameter sub-gsu, fill dbf field.
      * dbf allocation is 1 to 1 accompanied by
      * gsu_scalar_ext allocation. */
 
-    struct gsu_scalar_ext *dbf_carrier = (struct gsu_scalar_ext *) zalloc(sizeof(struct gsu_scalar_ext));
+    dbf_carrier = (struct gsu_scalar_ext *) zshcalloc(sizeof(struct gsu_scalar_ext));
     dbf_carrier->std = gdbm_gsu_ext.std;
+    dbf_carrier->type = DB_KEY_TYPE_NO_KEY;
+    dbf_carrier->fdesc = gdbm_fdesc(dbf);
     dbf_carrier->dbf = dbf;
+    dbf_carrier->use_cache = 1;
+    if (zcache) {
+        dbf_carrier->use_cache = 0;
+    }
+    if (lazy) {
+        dbf_carrier->is_lazy = 1;
+    }
     tied_param->u.hash->tmpdata = (void *)dbf_carrier;
 
     /* Fill also file path field */
-    if (*resource_name != '/') {
+    if (*address != '/') {
         /* Code copied from check_autoload() */
-        resource_name = zhtricat(metafy(zgetcwd(), -1, META_HEAPDUP), "/", resource_name);
-        resource_name = xsymlink(resource_name, 1);
+        address = zhtricat(metafy(zgetcwd(), -1, META_HEAPDUP), "/", address);
+        address = xsymlink(address, 1);
     }
-    dbf_carrier->dbfile_path = ztrdup(resource_name);
+    dbf_carrier->dbfile_path = ztrdup(address);
+
+    addmodulefd(dbf_carrier->fdesc, FDT_INTERNAL);
+    zsh_db_arr_append(&zgdbm_tied, pmname);
+
     return 0;
 }
+/* }}} */
+/* FUNCTION: zguntie_cmd {{{ */
 
 /**/
 static int
-bin_zuntie(char *nam, char **args, Options ops, UNUSED(int func))
+zguntie_cmd(int rountie, char **args)
 {
     Param pm;
     char *pmname;
     int ret = 0;
 
     for (pmname = *args; *args++; pmname = *args) {
-	pm = (Param) paramtab->getnode(paramtab, pmname);
-	if(!pm) {
-	    zwarnnam(nam, "cannot untie %s", pmname);
-	    ret = 1;
-	    continue;
-	}
-	if (pm->gsu.h != &gdbm_hash_gsu) {
-	    zwarnnam(nam, "not a tied gdbm hash: %s", pmname);
-	    ret = 1;
-	    continue;
-	}
-
-	queue_signals();
-	if (OPT_ISSET(ops,'u'))
-	    gdbmuntie(pm);	/* clear read-only-ness */
-	if (unsetparam_pm(pm, 0, 1)) {
-	    /* assume already reported */
-	    ret = 1;
-	}
-	unqueue_signals();
+        pm = (Param) paramtab->getnode(paramtab, pmname);
+        if(!pm) {
+            zwarn("cannot untie %s", pmname);
+            ret = 1;
+            continue;
+        }
+        if (pm->gsu.h != &gdbm_hash_gsu) {
+            zwarn("not a tied gdbm hash: %s", pmname);
+            ret = 1;
+            continue;
+        }
+
+        queue_signals();
+        if (rountie) {
+            pm->node.flags &= ~PM_READONLY;
+        }
+        if (unsetparam_pm(pm, 0, 1)) {
+            /* assume already reported */
+            ret = 1;
+        }
+        unqueue_signals();
     }
 
     return ret;
 }
+/* }}} */
+/* FUNCTION: gdbmpath_cmd{{{ */
 
 /**/
 static int
-bin_zgdbmpath(char *nam, char **args, Options ops, UNUSED(int func))
+gdbmpath_cmd(char *pmname)
 {
     Param pm;
-    char *pmname;
-
-    pmname = *args;
 
     if (!pmname) {
-        zwarnnam(nam, "parameter name (whose path is to be written to $REPLY) is required");
+        zwarn("parameter name (whose path is to be written to $REPLY) not given");
         return 1;
     }
 
     pm = (Param) paramtab->getnode(paramtab, pmname);
     if(!pm) {
-        zwarnnam(nam, "no such parameter: %s", pmname);
+        zwarn("no such parameter: %s", pmname);
         return 1;
     }
 
     if (pm->gsu.h != &gdbm_hash_gsu) {
-        zwarnnam(nam, "not a tied gdbm parameter: %s", pmname);
+        zwarn("not a tied gdbm parameter: %s", pmname);
         return 1;
     }
 
@@ -261,6 +356,49 @@ bin_zgdbmpath(char *nam, char **args, Options ops, UNUSED(int func))
 
     return 0;
 }
+/* }}} */
+/* FUNCTION: gdbmclear_cmd {{{ */
+
+/**/
+static int
+gdbmclear_cmd(char *pmname, char *key)
+{
+    Param pm, val_pm;
+    HashTable ht;
+    HashNode hn;
+
+    if (!pmname) {
+        zwarn("parameter name (whose read-cache is to be cleared) is required");
+        return 1;
+    }
+    if (!key) {
+        zwarn("hash-key (whose read-cache is to be cleared) is required");
+        return 1;
+    }
+
+    pm = (Param) paramtab->getnode(paramtab, pmname);
+    if(!pm) {
+        zwarnnam("no such parameter: %s", pmname);
+        return 1;
+    }
+
+    if (pm->gsu.h != &gdbm_hash_gsu) {
+        zwarnnam("not a tied gdbm parameter: %s", pmname);
+        return 1;
+    }
+
+    ht = pm->u.hash;
+    hn = gethashnode2( ht, key );
+    val_pm = (Param) hn;
+    if (val_pm) {
+        val_pm->node.flags &= ~(PM_UPTODATE);
+    }
+
+    return 0;
+}
+/* }}} */
+
+/* FUNCTION: gdbmgetfn {{{ */
 
 /*
  * The param is actual param in hash – always, because
@@ -269,9 +407,7 @@ bin_zgdbmpath(char *nam, char **args, Options ops, UNUSED(int func))
  * 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, as no other writer
- * can exist. This would remove subtle hcalloc(1) leak.
+ * contain such key.
  */
 
 /**/
@@ -279,7 +415,8 @@ static char *
 gdbmgetfn(Param pm)
 {
     datum key, content;
-    int ret;
+    int ret, umlen;
+    char *umkey;
     GDBM_FILE dbf;
 
     /* Key already retrieved? There is no sense of asking the
@@ -291,14 +428,14 @@ gdbmgetfn(Param pm)
      * - if we are writers, we for sure have newest copy of data
      * - if we are readers, we for sure have newest copy of data
      */
-    if ( pm->node.flags & PM_UPTODATE ) {
-        return pm->u.str ? pm->u.str : (char *) hcalloc(1);
+    if ((pm->node.flags & PM_UPTODATE) && ((struct gsu_scalar_ext *)pm->gsu.s)->use_cache) {
+        return pm->u.str ? pm->u.str : "";
     }
 
     /* Unmetafy key. GDBM fits nice into this
      * process, as it uses length of data */
-    int umlen = 0;
-    char *umkey = unmetafy_zalloc(pm->node.nam,&umlen);
+    umlen = 0;
+    umkey = zsh_db_unmetafy_zalloc(pm->node.nam,&umlen);
 
     key.dptr = umkey;
     key.dsize = umlen;
@@ -319,9 +456,11 @@ gdbmgetfn(Param pm)
         /* Metafy returned data. All fits - metafy
          * can obtain data length to avoid using \0 */
         pm->u.str = metafy(content.dptr, content.dsize, META_DUP);
+        /* gdbm allocates with malloc */
+        free(content.dptr);
 
         /* Free key, restoring its original length */
-        set_length(umkey, umlen);
+        zsh_db_set_length(umkey, umlen);
         zsfree(umkey);
 
         /* Can return pointer, correctly saved inside hash */
@@ -329,12 +468,13 @@ gdbmgetfn(Param pm)
     }
 
     /* Free key, restoring its original length */
-    set_length(umkey, umlen);
+    zsh_db_set_length(umkey, umlen);
     zsfree(umkey);
 
-    /* Can this be "" ? */
-    return (char *) hcalloc(1);
+    return "";
 }
+/* }}} */
+/* FUNCTION: gdbmsetfn {{{ */
 
 /**/
 static void
@@ -361,16 +501,16 @@ gdbmsetfn(Param pm, char *val)
 
     /* Database */
     dbf = ((struct gsu_scalar_ext *)pm->gsu.s)->dbf;
-    if (dbf) {
+    if (dbf && no_database_action == 0) {
         int umlen = 0;
-        char *umkey = unmetafy_zalloc(pm->node.nam,&umlen);
+        char *umkey = zsh_db_unmetafy_zalloc(pm->node.nam,&umlen);
 
         key.dptr = umkey;
         key.dsize = umlen;
 
         if (val) {
             /* Unmetafy with exact zalloc size */
-            char *umval = unmetafy_zalloc(val,&umlen);
+            char *umval = zsh_db_unmetafy_zalloc(val,&umlen);
 
             /* Store */
             content.dptr = umval;
@@ -378,18 +518,19 @@ gdbmsetfn(Param pm, char *val)
             (void)gdbm_store(dbf, key, content, GDBM_REPLACE);
 
             /* Free */
-            set_length(umval, umlen);
+            zsh_db_set_length(umval, umlen);
             zsfree(umval);
         } else {
             (void)gdbm_delete(dbf, key);
         }
 
         /* Free key */
-        set_length(umkey, key.dsize);
+        zsh_db_set_length(umkey, key.dsize);
         zsfree(umkey);
     }
 }
-
+/* }}} */
+/* FUNCTION: gdbmunsetfn {{{ */
 /**/
 static void
 gdbmunsetfn(Param pm, UNUSED(int um))
@@ -397,6 +538,9 @@ gdbmunsetfn(Param pm, UNUSED(int um))
     /* Set with NULL */
     gdbmsetfn(pm, NULL);
 }
+/* }}} */
+
+/* FUNCTION: getgdbmnode {{{ */
 
 /**/
 static HashNode
@@ -427,17 +571,19 @@ getgdbmnode(HashTable ht, const char *name)
         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
+        ht->addnode( ht, ztrdup( name ), val_pm ); /* sets pm->node.nam */
     }
 
     return (HashNode) val_pm;
 }
+/* }}} */
+/* FUNCTION: scangdbmkeys {{{ */
 
 /**/
 static void
 scangdbmkeys(HashTable ht, ScanFunc func, int flags)
 {
-    datum key;
+    datum key, prev_key;
     GDBM_FILE dbf = ((struct gsu_scalar_ext *)ht->tmpdata)->dbf;
 
     /* Iterate keys adding them to hash, so
@@ -452,14 +598,19 @@ scangdbmkeys(HashTable ht, ScanFunc func, int flags)
         HashNode hn = getgdbmnode(ht, zkey);
         zsfree( zkey );
 
-	func(hn, flags);
+        func(hn, flags);
 
         /* Iterate - no problem as interfacing Param
          * will do at most only fetches, not stores */
+        prev_key = key;
         key = gdbm_nextkey(dbf, key);
+        free(prev_key.dptr);
     }
 
 }
+/* }}} */
+
+/* FUNCTION: gdbmhashsetfn {{{ */
 
 /*
  * Replace database with new hash
@@ -475,83 +626,97 @@ gdbmhashsetfn(Param pm, HashTable ht)
     datum key, content;
 
     if (!pm->u.hash || pm->u.hash == ht)
-	return;
+        return;
 
     if (!(dbf = ((struct gsu_scalar_ext *)pm->u.hash->tmpdata)->dbf))
-	return;
+        return;
 
     key = gdbm_firstkey(dbf);
     while (key.dptr) {
-	queue_signals();
-	(void)gdbm_delete(dbf, key);
-	free(key.dptr);
-	unqueue_signals();
-	key = gdbm_firstkey(dbf);
+        queue_signals();
+        (void)gdbm_delete(dbf, key);
+        free(key.dptr);
+        unqueue_signals();
+        key = gdbm_firstkey(dbf);
     }
 
-    /* just deleted everything, clean up */
-    (void)gdbm_reorganize(dbf);
+    /* Just deleted everything, clean up if no new data.
+     * User can also reorganize via gdbmtool. */
+    if (!ht || ht->hsize == 0) {
+        (void)gdbm_reorganize(dbf);
+    }
+
+    no_database_action = 1;
+    emptyhashtable(pm->u.hash);
+    no_database_action = 0;
 
     if (!ht)
-	return;
+        return;
 
-     /* Put new strings into database, waiting
-      * for their interfacing-Params to be created */
+    /* Put new strings into database, waiting
+     * for their interfacing-Params to be created */
 
-    for (i = 0; i < ht->hsize; i++)
-	for (hn = ht->nodes[i]; hn; hn = hn->next) {
-	    struct value v;
+    for (i = 0; i < ht->hsize; i++) {
+        for (hn = ht->nodes[i]; hn; hn = hn->next) {
+            struct value v;
+            int umlen = 0;
+            char *umkey, *umval;
 
-	    v.isarr = v.flags = v.start = 0;
-	    v.end = -1;
-	    v.arr = NULL;
-	    v.pm = (Param) hn;
+            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);
+            umkey = zsh_db_unmetafy_zalloc(v.pm->node.nam,&umlen);
 
-	    key.dptr = umkey;
-	    key.dsize = umlen;
+            key.dptr = umkey;
+            key.dsize = umlen;
 
-	    queue_signals();
+            queue_signals();
 
             /* Unmetafy */
-            char *umval = unmetafy_zalloc(getstrvalue(&v),&umlen);
+            umval = zsh_db_unmetafy_zalloc(getstrvalue(&v),&umlen);
 
             /* Store */
-	    content.dptr = umval;
-	    content.dsize = umlen;
-	    (void)gdbm_store(dbf, key, content, GDBM_REPLACE);	
+            content.dptr = umval;
+            content.dsize = umlen;
+            (void)gdbm_store(dbf, key, content, GDBM_REPLACE);
 
-            /* Free - unmetafy_zalloc allocates exact required
+            /* Free - zsh_db_unmetafy_zalloc allocates exact required
              * space, however unmetafied string can have zeros
              * in content, so we must first fill with non-0 bytes */
-            set_length(umval, content.dsize);
+            zsh_db_set_length(umval, content.dsize);
             zsfree(umval);
-            set_length(umkey, key.dsize);
+            zsh_db_set_length(umkey, key.dsize);
             zsfree(umkey);
 
-	    unqueue_signals();
-	}
+            unqueue_signals();
+        }
+    }
+    /* We reuse our hash, the input is to be deleted */
+    deleteparamtable(ht);
 }
+/* }}} */
+/* FUNCTION: gdbmuntie {{{*/
 
 /**/
 static void
 gdbmuntie(Param pm)
 {
-    GDBM_FILE dbf = ((struct gsu_scalar_ext *)pm->u.hash->tmpdata)->dbf;
+    struct gsu_scalar_ext *gsu_ext = (struct gsu_scalar_ext *)pm->u.hash->tmpdata;
+    GDBM_FILE dbf = gsu_ext->dbf;
     HashTable ht = pm->u.hash;
 
     if (dbf) { /* paranoia */
-	fdtable[gdbm_fdesc(dbf)] = FDT_UNUSED;
+        fdtable[gsu_ext->fdesc] = FDT_UNUSED;
         gdbm_close(dbf);
 
         /* Let hash fields know there's no backend */
         ((struct gsu_scalar_ext *)ht->tmpdata)->dbf = NULL;
 
         /* Remove from list of tied parameters */
-        remove_tied_name(pm->node.nam);
+        zsh_db_filter_arr(&zgdbm_tied, pm->node.nam);
     }
 
     /* for completeness ... createspecialhash() should have an inverse */
@@ -561,19 +726,22 @@ gdbmuntie(Param pm)
     pm->node.flags &= ~(PM_SPECIAL|PM_READONLY);
     pm->gsu.h = &stdhash_gsu;
 }
-
+/* }}} */
+/* FUNCTION: gdbmhashunsetfn {{{ */
 /**/
 static void
 gdbmhashunsetfn(Param pm, UNUSED(int exp))
 {
+    struct gsu_scalar_ext * gsu_ext;
+
     gdbmuntie(pm);
 
     /* Remember custom GSU structure assigned to
      * u.hash->tmpdata before hash gets deleted */
-    struct gsu_scalar_ext * gsu_ext = pm->u.hash->tmpdata;
+    gsu_ext = pm->u.hash->tmpdata;
 
-    /* Uses normal unsetter. Will delete all owned
-     * parameters and also hashtable. */
+    /* Uses normal unsetter (because gdbmuntie is called above).
+     * Will delete all owned field-parameters and also hashtable. */
     pm->gsu.h->setfn(pm, NULL);
 
     /* Don't need custom GSU structure with its
@@ -583,14 +751,20 @@ gdbmhashunsetfn(Param pm, UNUSED(int exp))
 
     pm->node.flags |= PM_UNSET;
 }
+/* }}} */
 
-static struct features module_features = {
-    bintab, sizeof(bintab)/sizeof(*bintab),
+/* ARRAY: module_features {{{ */
+static struct features module_features =
+{
+    NULL, 0,
     NULL, 0,
     NULL, 0,
     patab, sizeof(patab)/sizeof(*patab),
     0
 };
+/* }}} */
+
+/* FUNCTION: setup_ {{{ */
 
 /**/
 int
@@ -598,7 +772,8 @@ setup_(UNUSED(Module m))
 {
     return 0;
 }
-
+/* }}} */
+/* FUNCTION: features_ {{{ */
 /**/
 int
 features_(Module m, char ***features)
@@ -606,6 +781,8 @@ features_(Module m, char ***features)
     *features = featuresarray(m, &module_features);
     return 0;
 }
+/* }}} */
+/* FUNCTION: enables_ {{{ */
 
 /**/
 int
@@ -613,34 +790,45 @@ enables_(Module m, int **enables)
 {
     return handlefeatures(m, &module_features, enables);
 }
+/* }}} */
+/* FUNCTION: boot_ {{{ */
 
 /**/
 int
 boot_(UNUSED(Module m))
 {
     zgdbm_tied = zshcalloc((1) * sizeof(char *));
+    zsh_db_register_backend("db/gdbm", gdbm_main_entry);
     return 0;
 }
+/* }}} */
+/* FUNCTION: cleanup_ {{{ */
 
 /**/
 int
 cleanup_(Module m)
 {
+    zsh_db_unregister_backend("db/gdbm");
+
     /* This frees `zgdbm_tied` */
     return setfeatureenables(m, &module_features, NULL);
 }
-
+/* }}} */
+/* FUNCTION: finish_ {{{ */
 /**/
 int
 finish_(UNUSED(Module m))
 {
     return 0;
 }
+/* }}} */
 
 /*********************
  * Utility functions *
  *********************/
 
+/* FUNCTION: createhash {{{ */
+
 static Param createhash( char *name, int flags ) {
     Param pm;
     HashTable ht;
@@ -651,137 +839,51 @@ static Param createhash( char *name, int flags ) {
     }
 
     if (pm->old)
-	pm->level = locallevel;
+        pm->level = locallevel;
 
     /* This creates standard hash. */
-    ht = pm->u.hash = newparamtable(32, name);
+    ht = pm->u.hash = newparamtable(17, name);
     if (!pm->u.hash) {
         paramtab->removenode(paramtab, name);
         paramtab->freenode(&pm->node);
-        zwarnnam(name, "Out of memory when allocating hash");
+        zwarnnam(name, "out of memory when allocating hash");
+        return NULL;
     }
 
+    /* Does free Param (unsetfn is called) */
+    ht->freenode = zsh_db_freeparamnode;
+
     /* These provide special features */
     ht->getnode = ht->getnode2 = getgdbmnode;
     ht->scantab = scangdbmkeys;
 
     return pm;
 }
-
-/*
- * Adds parameter name to `zgdbm_tied`
- */
-
-static int append_tied_name( const char *name ) {
-    int old_len = arrlen(zgdbm_tied);
-    char **new_zgdbm_tied = zshcalloc( (old_len+2) * sizeof(char *));
-
-    /* Copy */
-    char **p = zgdbm_tied;
-    char **dst = new_zgdbm_tied;
-    while (*p) {
-        *dst++ = *p++;
+/* }}} */
+/* FUNCTION: is_tied_cmd {{{ */
+static int
+is_tied_cmd(char *pmname)
+{
+    Param pm = (Param) paramtab->getnode(paramtab, pmname);
+    if(!pm) {
+        return 1; /* false */
     }
 
-    /* Append new one */
-    *dst = ztrdup(name);
-
-    /* Substitute, free old one */
-    zfree(zgdbm_tied, sizeof(char *) * (old_len + 1));
-    zgdbm_tied = new_zgdbm_tied;
-
-    return 0;
+    return 1 - is_tied(pm); /* negation for shell-code */
 }
+/* }}} */
+/* FUNCTION: is_tied {{{ */
 
-/*
- * Removes parameter name from `zgdbm_tied`
- */
-
-static int remove_tied_name( const char *name ) {
-    int old_len = arrlen(zgdbm_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 = zgdbm_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(zgdbm_tied);
-    if (new_len != old_len) {
-        char **new_zgdbm_tied = zshcalloc((new_len+1) * sizeof(char *));
-
-        /* Copy */
-        p = zgdbm_tied;
-        char **dst = new_zgdbm_tied;
-        while (*p) {
-            *dst++ = *p++;
-        }
-        *dst = NULL;
-
-        /* Substitute, free old one */
-        zfree(zgdbm_tied, sizeof(char *) * (old_len + 1));
-        zgdbm_tied = new_zgdbm_tied;
+static int
+is_tied(Param pm)
+{
+    if (pm->gsu.h == &gdbm_hash_gsu ) {
+        return 1;
     }
 
     return 0;
 }
-
-/*
- * 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;
-}
-
-/*
- * 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]=' ';
-    }
-}
+/* }}} */
 
 #else
 # error no gdbm
diff --git a/Src/Modules/db_gdbm.mdd b/Src/Modules/db_gdbm.mdd
index 210c221..4667222 100644
--- a/Src/Modules/db_gdbm.mdd
+++ b/Src/Modules/db_gdbm.mdd
@@ -7,6 +7,8 @@ fi
 '
 load=no
 
-autofeatures="b:ztie b:zuntie b:zgdbmpath p:zgdbm_tied"
+autofeatures="p:zgdbm_tied"
+
+moddeps="zsh/db"
 
 objects="db_gdbm.o"
diff --git a/Test/V11db_gdbm.ztst b/Test/V11db_gdbm.ztst
index 6d74cef..499108e 100644
--- a/Test/V11db_gdbm.ztst
+++ b/Test/V11db_gdbm.ztst
@@ -4,16 +4,21 @@
 
 %prep
 
- modname="zsh/db/gdbm"
+ module_path=( `pwd`/Modules )
+ modname1="zsh/db"
+ modname2="zsh/db/gdbm"
  dbfile=db.gdbm
- if ! zmodload $modname 2>/dev/null; then
-   ZTST_unimplemented="can't load $modname module for testing"
+ if ! zmodload $modname1 2>/dev/null; then
+   ZTST_unimplemented="can't load $modname1 module for testing"
+ fi
+ if ! zmodload $modname2 2>/dev/null; then
+   ZTST_unimplemented="can't load $modname2 module for testing"
  fi
  rm -f db.gdbm
 
 %test
 
- (zmodload -u $modname && zmodload $modname)
+ (zmodload -u $modname2 && zmodload $modname2)
 0:unload and reload the module without crashing
 
  ztie -d db/gdbm -f $dbfile dbase
@@ -304,23 +309,40 @@
 >漢字
 
  ztie -d db/gdbm -f $dbfile dbase
- zgdbmpath dbase
+ ztaddress dbase
  [[ $REPLY = */Test/db.gdbm ]] && echo correct
  zuntie dbase
  ztie -r -d db/gdbm -f $dbfile dbase
- zgdbmpath dbase
+ ztaddress dbase
  [[ $REPLY = */Test/db.gdbm ]] && echo correct
  zuntie -u dbase
-0:zgdbmpath builtin
+0:ztaddress builtin
 >correct
 >correct
 
  ztie -d db/gdbm -f $dbfile dbase
+ dbase[testkey]=value1
  fun() { while read line; do echo $line; done }
- eval "dbase[testkey]=value1" | fun
+ eval "dbase[testkey]=value2" | fun
+ echo $dbase[testkey]
+ ztclear dbase testkey
  echo $dbase[testkey]
 0:Test store in forked Zsh
 >value1
+>value2
+
+ ztie -d db/gdbm -f $dbfile -z dbase
+ dbase[testkey]=value1
+ fun() { while read line; do echo $line; done }
+ eval "dbase[testkey]=value2" | fun
+ echo $dbase[testkey]
+0:Test store in forked Zsh, with -z zero-cache option
+>value2
+
+ print -rl -- "${(okv@)zdb_backends}"
+0:Test zdb_backends
+>db/gdbm
+>loaded
 
 %clean
 

[-- Attachment #3: emacs_folds.png --]
[-- Type: image/png, Size: 145220 bytes --]

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

* Re: [PATCH] 2 modules, zsh/db, zsh/gdbm, bug-fixes
  2017-06-25  8:06   ` Sebastian Gniazdowski
@ 2017-06-25 11:22     ` Sebastian Gniazdowski
  2017-06-25 22:52     ` Bart Schaefer
  1 sibling, 0 replies; 15+ messages in thread
From: Sebastian Gniazdowski @ 2017-06-25 11:22 UTC (permalink / raw)
  To: Bart Schaefer, zsh-workers

On 25 czerwca 2017 at 10:06:04, Sebastian Gniazdowski (psprint@zdharma.org) wrote:
> Sending state-of-art patch. Did multiple test runs on different machines (without 
> obvious reason, the code didn't change), as always.

Got hands on a new machine, medium-recent Arch Linux. Tests and Valgrind tests passed. I'm doing this broad-testing because nuances come up often, for VATS it was weird suffix added to static function (zgtie_cmd.istr8, something like that), for GDBM it was the NFS problem.

-- 
Sebastian Gniazdowski
psprint /at/ zdharma.org


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

* Re: [PATCH] 2 modules, zsh/db, zsh/gdbm, bug-fixes
  2017-06-25  8:06   ` Sebastian Gniazdowski
  2017-06-25 11:22     ` Sebastian Gniazdowski
@ 2017-06-25 22:52     ` Bart Schaefer
  2017-06-27  5:46       ` Sebastian Gniazdowski
  1 sibling, 1 reply; 15+ messages in thread
From: Bart Schaefer @ 2017-06-25 22:52 UTC (permalink / raw)
  To: zsh-workers

On Jun 25, 10:06am, Sebastian Gniazdowski wrote:
}
} As for folding, I attach Emacs screenshot, to show it really helps.
} Learned the folds from reading NERDTree source (vim plugin), it was
} huge, but had the folds and they helped.

Is this really more helpful than selective-display?

Ctrl-u 1 Ctrl-x $

Gives you e.g. (copied from builtin.c):

/* Print a builtin */

/**/
static void
printbuiltinnode(HashNode hn, int printflags)
{...
}

/**/
static void
freebuiltinnode(HashNode hn)
{...
}


I find that more useful than a comment with just the function name, and
it's easier to avoid redundancy or the comment being out of date with
respect to what's inside it, as well.  Also if the 4-space indentation
has been followed carefully enough, you incrementally unfold by adding
4 to the previous selective-display level, e.g.

Ctrl-u 5 Ctrl-x $

/* Print a builtin */

/**/
static void
printbuiltinnode(HashNode hn, int printflags)
{
    Builtin bn = (Builtin) hn;

    if (printflags & PRINT_WHENCE_WORD) {...
    }

    if (printflags & PRINT_WHENCE_CSH) {...
    }

    if (printflags & PRINT_WHENCE_VERBOSE) {...
    }

    /* default is name only */
    printf("%s\n", bn->node.nam);
}


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

* Re: [PATCH] 2 modules, zsh/db, zsh/gdbm, bug-fixes
  2017-06-25  0:50         ` Daniel Shahaf
@ 2017-06-26  2:40           ` Sebastian Gniazdowski
  0 siblings, 0 replies; 15+ messages in thread
From: Sebastian Gniazdowski @ 2017-06-26  2:40 UTC (permalink / raw)
  To: Daniel Shahaf, zsh-workers

On 25 czerwca 2017 at 02:50:23, Daniel Shahaf (d.s@daniel.shahaf.name) wrote:
> Isn't there a permanent link to a description of the bug? Preferably in
> the gdbm bug tracker, or at least on their mailing list? That's needed
> so people in the future can know why the workaround was added and judge
> whether it can be removed.

No, there's no mailing list

-- 
Sebastian Gniazdowski
psprint /at/ zdharma.org


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

* Re: [PATCH] 2 modules, zsh/db, zsh/gdbm, bug-fixes
  2017-06-25 22:52     ` Bart Schaefer
@ 2017-06-27  5:46       ` Sebastian Gniazdowski
  2017-06-27  8:39         ` Daniel Shahaf
  0 siblings, 1 reply; 15+ messages in thread
From: Sebastian Gniazdowski @ 2017-06-27  5:46 UTC (permalink / raw)
  To: Bart Schaefer, zsh-workers


On 26 czerwca 2017 at 00:52:32, Bart Schaefer (schaefer@brasslantern.com) wrote:
> On Jun 25, 10:06am, Sebastian Gniazdowski wrote:
> }
> } As for folding, I attach Emacs screenshot, to show it really helps.
> } Learned the folds from reading NERDTree source (vim plugin), it was
> } huge, but had the folds and they helped.
>  
> Is this really more helpful than selective-display?
>  
> Ctrl-u 1 Ctrl-x $

Ok, I've removed folding. Added documentation. Small code change – use zfree() not zsfree() because sizes are known. Tested on 2 machines with Valgrind, on one other via normal V11 test.

Code and docs-patch is here:

https://github.com/zdharma/hacking-private/tree/master/2db

--  
Sebastian Gniazdowski
psprint /at/ zdharma.org


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

* Re: [PATCH] 2 modules, zsh/db, zsh/gdbm, bug-fixes
  2017-06-27  5:46       ` Sebastian Gniazdowski
@ 2017-06-27  8:39         ` Daniel Shahaf
  0 siblings, 0 replies; 15+ messages in thread
From: Daniel Shahaf @ 2017-06-27  8:39 UTC (permalink / raw)
  To: zsh-workers

Sebastian Gniazdowski wrote on Tue, 27 Jun 2017 07:46 +0200:
> Ok, I've removed folding.

Thanks.

Daniel


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

end of thread, other threads:[~2017-06-27  8:39 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-06-22  9:04 [PATCH] 2 modules, zsh/db, zsh/gdbm, bug-fixes Sebastian Gniazdowski
2017-06-22  9:05 ` Sebastian Gniazdowski
2017-06-23  9:29   ` Bart Schaefer
2017-06-23  8:36 ` Bart Schaefer
2017-06-23 21:30   ` Daniel Shahaf
2017-06-24  3:53     ` Bart Schaefer
2017-06-24  4:56       ` Sebastian Gniazdowski
2017-06-25  0:50         ` Daniel Shahaf
2017-06-26  2:40           ` Sebastian Gniazdowski
2017-06-24 17:36       ` Bart Schaefer
2017-06-25  8:06   ` Sebastian Gniazdowski
2017-06-25 11:22     ` Sebastian Gniazdowski
2017-06-25 22:52     ` Bart Schaefer
2017-06-27  5:46       ` Sebastian Gniazdowski
2017-06-27  8:39         ` Daniel Shahaf

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).