aboutsummaryrefslogtreecommitdiffstats
path: root/extension
diff options
context:
space:
mode:
Diffstat (limited to 'extension')
-rw-r--r--extension/ChangeLog5
-rw-r--r--extension/bindarr.c339
-rw-r--r--extension/dbarray.awk222
-rwxr-xr-xextension/steps2
-rw-r--r--extension/testdbarray.awk21
5 files changed, 589 insertions, 0 deletions
diff --git a/extension/ChangeLog b/extension/ChangeLog
index 75dc66a4..6fdd40e5 100644
--- a/extension/ChangeLog
+++ b/extension/ChangeLog
@@ -1,3 +1,8 @@
+2011-05-02 John Haque <j.eh@mchsi.com>
+
+ * bindarr.c, dbarray.awk, testdbarray.awk: New files.
+ * steps: Updated.
+
2011-04-24 John Haque <j.eh@mchsi.com>
* spec_array.c, spec_array.h, sparr.c, testsparr.awk: New files.
diff --git a/extension/bindarr.c b/extension/bindarr.c
new file mode 100644
index 00000000..f500b748
--- /dev/null
+++ b/extension/bindarr.c
@@ -0,0 +1,339 @@
+/*
+ * bindarr.c - routines for binding (attaching) user-defined functions
+ * to array and array elements.
+ */
+
+/*
+ * Copyright (C) 1986, 1988, 1989, 1991-2011 the Free Software Foundation, Inc.
+ *
+ * This file is part of GAWK, the GNU implementation of the
+ * AWK Programming Language.
+ *
+ * GAWK is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GAWK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#include "awk.h"
+
+/*
+ * Binding an array is basically the binding of functions to the internal
+ * triggers for reading and writing that array or an element of that array.
+ * This allows the user to define the set of behaviors for gawk arrays
+ * using gawk functions. With arrays you can assign and read values of
+ * specific elements, provide list of indices and values, and tell if a
+ * certain index exists or not. A variable can be "tied" by including
+ * code which overrides any or all of the standard behaviors of awk arrays.
+ *
+ * See dbarray.awk and testdbarray.awk to learn how to bind an array
+ * to an external database for persistent storage.
+ */
+
+int plugin_is_GPL_compatible;
+
+static NODE **bind_array_lookup(NODE *, NODE *);
+static NODE **bind_array_exists(NODE *, NODE *);
+static NODE **bind_array_clear(NODE *, NODE *);
+static NODE **bind_array_remove(NODE *, NODE *);
+static NODE **bind_array_list(NODE *, NODE *);
+static NODE **bind_array_store(NODE *, NODE *);
+static NODE **bind_array_length(NODE *, NODE *);
+
+static afunc_t bind_array_func[] = {
+ (afunc_t) 0,
+ (afunc_t) 0,
+ bind_array_length,
+ bind_array_lookup,
+ bind_array_exists,
+ bind_array_clear,
+ bind_array_remove,
+ bind_array_list,
+ null_afunc, /* copy */
+ null_afunc, /* dump */
+ bind_array_store,
+};
+
+enum { INIT, FINI, COUNT, EXISTS, LOOKUP,
+ STORE, DELETE, CLEAR, FETCHALL };
+
+static const char *const bfn[] = {
+ "init", "fini", "count", "exists", "lookup",
+ "store", "delete", "clear", "fetchall",
+};
+
+typedef struct {
+ NODE *func[sizeof(bfn)/sizeof(char *)];
+ NODE *arg0;
+} array_t;
+
+static NODE *call_func(NODE *func, NODE **arg, int arg_count);
+static long array_func_call(NODE *, NODE *, int);
+
+
+/* bind_array_length -- find the number of elements in the array */
+
+static NODE **
+bind_array_length(NODE *symbol, NODE *subs ATTRIBUTE_UNUSED)
+{
+ static NODE *length_node;
+
+ symbol->table_size = array_func_call(symbol, NULL, COUNT);
+ length_node = symbol;
+ return & length_node;
+}
+
+/* bind_array_lookup --- find element in the array; return a pointer to value. */
+
+static NODE **
+bind_array_lookup(NODE *symbol, NODE *subs)
+{
+ NODE *xn = symbol->xarray;
+ (void) array_func_call(symbol, subs, LOOKUP);
+ return xn->alookup(xn, subs);
+}
+
+/*
+ * bind_array_exists --- test whether the array element symbol[subs] exists or not,
+ * return pointer to value if it does.
+ */
+
+static NODE **
+bind_array_exists(NODE *symbol, NODE *subs)
+{
+ NODE *xn = symbol->xarray;
+ (void) array_func_call(symbol, subs, EXISTS);
+ return xn->aexists(xn, subs);
+}
+
+/* bind_array_clear --- flush all the values in symbol[] */
+
+static NODE **
+bind_array_clear(NODE *symbol, NODE *subs ATTRIBUTE_UNUSED)
+{
+ NODE *xn = symbol->xarray;
+ (void) array_func_call(symbol, NULL, CLEAR);
+ return xn->aclear(xn, NULL);
+}
+
+/* bind_array_remove --- if subs is already in the table, remove it. */
+
+static NODE **
+bind_array_remove(NODE *symbol, NODE *subs)
+{
+ NODE *xn = symbol->xarray;
+ (void) array_func_call(symbol, subs, DELETE);
+ return xn->aremove(xn, subs);
+}
+
+/* bind_array_store --- update the value for the SUBS */
+
+static NODE **
+bind_array_store(NODE *symbol, NODE *subs)
+{
+ (void) array_func_call(symbol, subs, STORE);
+ return NULL;
+}
+
+/* bind_array_list --- return a list of array items */
+
+static NODE**
+bind_array_list(NODE *symbol, NODE *akind)
+{
+ NODE *xn = symbol->xarray;
+ (void) array_func_call(symbol, NULL, FETCHALL);
+ return xn->alist(xn, akind);
+}
+
+
+/* array_func_call --- call user-defined array routine */
+
+static long
+array_func_call(NODE *symbol, NODE *arg1, int fi)
+{
+ NODE *argp[3];
+ NODE *retval;
+ long ret;
+ int i = 0;
+ array_t *aq;
+
+ aq = symbol->a_opaque;
+ if (! aq) /* an array routine invoked from the same or another routine */
+ fatal(_("bind_array: cannot access bound array, operation not allowed"));
+ symbol->a_opaque = NULL; /* avoid infinite recursion */
+
+ argp[i++] = symbol->xarray;
+ argp[i++] = aq->arg0;
+ if (arg1 != NULL)
+ argp[i++] = arg1;
+
+ retval = call_func(aq->func[fi], argp, i);
+ symbol->a_opaque = aq;
+ force_number(retval);
+ ret = get_number_si(retval);
+ unref(retval);
+ if (ret < 0)
+ fatal(ERRNO_node->var_value->stlen > 0 ?
+ _("%s"), ERRNO_node->var_value->stptr :
+ _("unknown reason"));
+ return ret;
+}
+
+/* do_bind_array --- bind an array to user-defined functions */
+
+static NODE *
+do_bind_array(int nargs)
+{
+ NODE *symbol, *xn, *t, *td;
+ int i;
+ array_t *aq;
+ char *aname;
+
+ symbol = get_array_argument(0, FALSE);
+ assoc_clear(symbol);
+
+ emalloc(aq, array_t *, sizeof(array_t), "do_bind_array");
+ memset(aq, '\0', sizeof(array_t));
+
+ t = get_array_argument(1, FALSE);
+
+ for (i = 0; i < sizeof(bfn)/sizeof(char *); i++) {
+ NODE *subs, *val, *f;
+
+ subs = make_string(bfn[i], strlen(bfn[i]));
+ val = in_array(t, subs);
+ unref(subs);
+ if (val == NULL) {
+ if (i != INIT && i != FINI)
+ fatal(_("bind_array: array element `%s[\"%s\"]' not defined"),
+ t->vname, bfn[i]);
+ continue;
+ }
+
+ force_string(val);
+ f = lookup(val->stptr);
+ if (f == NULL || f->type != Node_func)
+ fatal(_("bind_array: function `%s' is not defined"), val->stptr);
+ aq->func[i] = f;
+ }
+
+ /* copy the array -- this is passed as the second argument to the functions */
+ emalloc(aname, char *, strlen(t->vname) + 2, "do_bind_array");
+ aname[0] = '~'; /* any illegal character */
+ strcpy(& aname[1], symbol->vname);
+ td = make_array();
+ td->vname = aname;
+ assoc_copy(t, td);
+ aq->arg0 = td;
+
+ /* internal array for the actual storage */
+ xn = make_array();
+ xn->vname = symbol->vname; /* shallow copy */
+ xn->flags |= XARRAY;
+ symbol->a_opaque = aq;
+ symbol->array_funcs = bind_array_func;
+ symbol->xarray = xn;
+
+ if (aq->func[INIT] != NULL)
+ (void) array_func_call(symbol, NULL, INIT);
+
+ return make_number(0);
+}
+
+/* do_unbind_array --- unbind an array */
+
+static NODE *
+do_unbind_array(int nargs)
+{
+ NODE *symbol, *xn, *td;
+ array_t *aq;
+
+ symbol = get_array_argument(0, FALSE);
+ if (symbol->array_funcs != bind_array_func)
+ fatal(_("unbind_array: `%s' is not a bound array"), array_vname(symbol));
+
+ aq = symbol->a_opaque;
+ if (aq->func[FINI] != NULL)
+ (void) array_func_call(symbol, NULL, FINI);
+
+ td = aq->arg0;
+ assoc_clear(td);
+ efree(td->vname);
+ freenode(td);
+ efree(aq);
+
+ /* promote xarray to symbol */
+ xn = symbol->xarray;
+ xn->flags &= ~XARRAY;
+ xn->parent_array = symbol->parent_array;
+ *symbol = *xn;
+ freenode(xn);
+
+ return make_number(0);
+}
+
+
+/* call_func --- call a user-defined gawk function */
+
+static NODE *
+call_func(NODE *func, NODE **arg, int arg_count)
+{
+ NODE *ret;
+ INSTRUCTION *code;
+ extern int currule;
+ int i, save_rule = 0;
+
+ if (arg_count > func->param_cnt)
+ fatal(_("function `%s' called with too many parameters"), func->vname);
+
+ /* make function call instructions */
+ code = bcalloc(Op_func_call, 2, 0);
+ code->func_body = func;
+ code->func_name = NULL; /* not needed, func_body already assigned */
+ (code + 1)->expr_count = arg_count;
+ code->nexti = bcalloc(Op_stop, 1, 0);
+
+ save_rule = currule; /* save current rule */
+ currule = 0;
+
+ /* push arguments onto stack */
+ for (i = 0; i < arg_count; i++) {
+ if (arg[i]->type == Node_val)
+ UPREF(arg[i]);
+ PUSH(arg[i]);
+ }
+
+ /* execute the function */
+ (void) interpret(code);
+
+ ret = POP_SCALAR(); /* the return value of the function */
+
+ /* restore current rule */
+ currule = save_rule;
+
+ /* free code */
+ bcfree(code->nexti);
+ bcfree(code);
+
+ return ret;
+}
+
+
+/* dlload --- load this library */
+
+NODE *
+dlload(NODE *obj, void *dl)
+{
+ make_builtin("bind_array", do_bind_array, 2);
+ make_builtin("unbind_array", do_unbind_array, 1);
+ return make_number((AWKNUM) 0);
+}
diff --git a/extension/dbarray.awk b/extension/dbarray.awk
new file mode 100644
index 00000000..e0a3c093
--- /dev/null
+++ b/extension/dbarray.awk
@@ -0,0 +1,222 @@
+# dbarray.awk -- persistent array with sqlite database backend
+
+# @load "bindarr"
+
+BEGIN {
+ extension("bindarr")
+}
+
+function _db_count(symbol, sq,
+ sth, ret, count)
+{
+ sth = sq["sqlc"]
+ printf "SELECT count(col1) FROM %s;\n", sq["table"] |& sth
+ close(sth, "to")
+ ret = (sth |& getline count)
+ if (close(sth) != 0 || ret <= 0)
+ return -1
+ return count
+}
+
+function _db_exists(symbol, sq, subs,
+ sth, ret, row, qsubs)
+{
+ if (! (subs in symbol)) {
+ sth = sq["sqlc"]
+
+ # double up single quotes
+ qsubs = gensub(/'/, "''", "g", subs)
+
+ printf "SELECT col2 FROM %s WHERE col1='%s';\n", sq["table"], qsubs |& sth
+ close(sth, "to")
+ ret = (sth |& getline row)
+ if (close(sth) != 0 || ret < 0)
+ return -1
+ if (ret == 0) # non-existent row
+ return 0
+ if (row == sq["null"])
+ symbol[subs] # install null string as value
+ else
+ symbol[subs] = row
+ }
+ return 0
+}
+
+function _db_lookup(symbol, sq, subs,
+ sth, ret, row, qsubs)
+{
+ if (! (subs in symbol)) {
+ sth = sq["sqlc"]
+
+ # double up single quotes
+ qsubs = gensub(/'/, "''", "g", subs)
+
+ printf "SELECT col2 FROM %s WHERE col1='%s';\n", sq["table"], qsubs |& sth
+ close(sth, "to")
+ ret = (sth |& getline row)
+ if (close(sth) != 0 || ret < 0)
+ return -1
+
+ if (ret > 0) {
+ if (row == sq["null"])
+ symbol[subs] # install null string as value
+ else
+ symbol[subs] = row
+ } else {
+ # Not there, install it with NULL as value
+ printf "INSERT INTO %s (col1) VALUES('%s');\n", sq["table"], qsubs |& sth
+ close(sth, "to")
+ ret = (sth |& getline)
+ if (close(sth) != 0 || ret < 0)
+ return -1
+ }
+ }
+ return 0
+}
+
+function _db_clear(symbol, sq,
+ sth, ret)
+{
+ sth = sq["sqlc"]
+ printf "DELETE FROM %s;\n", sq["table"] |& sth
+ close(sth, "to")
+ ret = (sth |& getline)
+ if (close(sth) != 0 || ret < 0)
+ return -1
+ return 0
+}
+
+function _db_delete(symbol, sq, subs,
+ sth, ret, qsubs)
+{
+ sth = sq["sqlc"]
+ qsubs = gensub(/'/, "''", "g", subs)
+ printf "DELETE FROM %s WHERE col1='%s';\n", sq["table"], qsubs |& sth
+ close(sth, "to")
+ ret = (sth |& getline)
+ if (close(sth) != 0 || ret < 0)
+ return -1
+ return 0
+}
+
+function _db_store(symbol, sq, subs,
+ sth, ret, qsubs, qval)
+{
+ sth = sq["sqlc"]
+
+ qval = gensub(/'/, "''", "g", symbol[subs])
+ qsubs = gensub(/'/, "''", "g", subs)
+ printf "UPDATE %s SET col2='%s' WHERE col1='%s';\n", \
+ sq["table"], qval, qsubs |& sth
+ close(sth, "to")
+ ret = (sth |& getline)
+ if (close(sth) != 0 || ret < 0)
+ return -1
+ return 0
+}
+
+function _db_fetchall(symbol, sq,
+ sth, ret, save_RS, save_FS)
+{
+ sth = sq["sqlc2"]
+
+ if (! sq["loaded"]) {
+ printf "SELECT col1, col2 FROM %s;\n", sq["table"] |& sth
+ close(sth, "to")
+ save_RS = RS
+ save_FS = FS
+ RS = "\n\n"
+ FS = "\n"
+ while ((ret = (sth |& getline)) > 0) {
+ sub(/^ *col1 = /, "", $1)
+ sub(/^ *col2 = /, "", $2)
+ if ($2 == sq["null"])
+ symbol[$1] # install null string as value
+ else
+ symbol[$1] = $2
+ }
+ RS = save_RS
+ FS = save_FS
+ if (ret < 0 || close(sth) != 0)
+ return -1
+ sq["loaded"] = 1
+ }
+}
+
+
+function _db_init(symbol, sq,
+ sth, table, ret)
+{
+ sth = sq["sqlc"]
+ table = sq["table"]
+
+ # check if table exists
+ printf ".tables %s\n", table |& sth
+ close(sth, "to")
+ ret = (sth |& getline)
+ if (close(sth) != 0 || ret < 0)
+ return -1
+ if (ret > 0 && $0 == table) {
+ # verify schema
+ printf ".schema %s\n", table |& sth
+ close(sth, "to")
+ ret = (sth |& getline)
+ if (close(sth) != 0 || ret <= 0)
+ return -1
+ if ($0 !~ /\(col1 TEXT PRIMARY KEY, col2 TEXT\)/) {
+ printf "table %s: Invalid column name or type(s)\n", table > "/dev/stderr"
+ return -1
+ }
+ } else {
+ # table does not exist, create it.
+ printf "CREATE TABLE %s (col1 TEXT PRIMARY KEY, col2 TEXT);\n", table |& sth
+ close(sth, "to")
+ ret = (sth |& getline)
+ if (close(sth) != 0 || ret < 0)
+ return -1
+ }
+ return 0
+}
+
+#function _db_fini(tie, a, subs) {}
+
+function db_bind(arr, database, table, sq)
+{
+ if (! database) {
+ print "db_bind: must specify a database name" > "/dev/stderr"
+ exit(1)
+ }
+
+ if (! table) {
+ print "db_bind: must specify a table name" > "/dev/stderr"
+ exit(1)
+ }
+
+ # string used by the sqlite3 client to represent NULL
+ sq["null"] = "(null)"
+
+ sq["sqlc"] = sprintf("sqlite3 -nullvalue '%s' %s", sq["null"], database)
+ # sqlite command used in _db_fetchall
+ sq["sqlc2"] = sprintf("sqlite3 -line -nullvalue '%s' %s", sq["null"], database)
+
+ sq["table"] = table
+
+ # register our array routines
+ sq["init"] = "_db_init"
+ sq["count"] = "_db_count"
+ sq["exists"] = "_db_exists"
+ sq["lookup"] = "_db_lookup"
+ sq["delete"] = "_db_delete"
+ sq["store"] = "_db_store"
+ sq["clear"] = "_db_clear"
+ sq["fetchall"] = "_db_fetchall"
+
+# sq["fini"] = "_db_fini";
+
+ bind_array(arr, sq)
+}
+
+function db_unbind(arr)
+{
+ unbind_array(arr)
+}
diff --git a/extension/steps b/extension/steps
index 0cd0042a..1abab9d2 100755
--- a/extension/steps
+++ b/extension/steps
@@ -15,6 +15,7 @@ gcc -fPIC -shared -Wall -DHAVE_CONFIG_H -c -O -g -I.. testarg.c
gcc -fPIC -shared -Wall -DHAVE_CONFIG_H -c -O -g -I.. rwarray.c
gcc -fPIC -shared -Wall -DHAVE_CONFIG_H -c -O -g -I.. spec_array.c
gcc -fPIC -shared -Wall -DHAVE_CONFIG_H -c -O -g -I.. sparr.c
+gcc -fPIC -shared -Wall -DHAVE_CONFIG_H -c -O -g -I.. bindarr.c
ld -o dl.so -shared dl.o
ld -o filefuncs.so -shared filefuncs.o
ld -o fork.so -shared fork.o
@@ -24,3 +25,4 @@ ld -o readfile.so -shared readfile.o
ld -o testarg.so -shared testarg.o
ld -o rwarray.so -shared rwarray.o
ld -o sparr.so -shared sparr.o spec_array.o
+ld -o bindarr.so -shared bindarr.o
diff --git a/extension/testdbarray.awk b/extension/testdbarray.awk
new file mode 100644
index 00000000..fd7fd595
--- /dev/null
+++ b/extension/testdbarray.awk
@@ -0,0 +1,21 @@
+@include "dbarray.awk"
+
+# $ ../gawk -f testdbarray.awk
+# $ ../gawk -f testdbarray.awk
+# ...
+# $ ../gawk -vINIT=1 -f testdbarray.awk
+
+
+BEGIN {
+ # bind array 'A' to the table 'table_A' in sqlite3 database 'testdb'
+ db_bind(A, "testdb", "table_A")
+
+ if (INIT) # detele table and start over
+ delete A
+
+ lenA = length(A)
+ A[++lenA] = strftime()
+ PROCINFO["sorted_in"] = "@ind_num_asc"
+ for (item in A)
+ print item, ":", A[item]
+}