murphy-db: initial commit of the MurphyDB
authorJanos Kovacs <jankovac503@gmail.com>
Sun, 25 Mar 2012 19:44:10 +0000 (22:44 +0300)
committerKrisztian Litkey <krisztian.litkey@intel.com>
Wed, 18 Apr 2012 13:32:09 +0000 (16:32 +0300)
48 files changed:
configure.ac
src/Makefile.am
src/murphy-db/Makefile.am [new file with mode: 0644]
src/murphy-db/include/Makefile.am [new file with mode: 0644]
src/murphy-db/include/murphy-db/assert.h [new file with mode: 0644]
src/murphy-db/include/murphy-db/handle.h [new file with mode: 0644]
src/murphy-db/include/murphy-db/hash.h [new file with mode: 0644]
src/murphy-db/include/murphy-db/list.h [new file with mode: 0644]
src/murphy-db/include/murphy-db/mdb.h [new file with mode: 0644]
src/murphy-db/include/murphy-db/mqi-types.h [new file with mode: 0644]
src/murphy-db/include/murphy-db/mqi.h [new file with mode: 0644]
src/murphy-db/include/murphy-db/mql.h [new file with mode: 0644]
src/murphy-db/include/murphy-db/result.h [new file with mode: 0644]
src/murphy-db/include/murphy-db/sequence.h [new file with mode: 0644]
src/murphy-db/include/murphy-db/statement.h [new file with mode: 0644]
src/murphy-db/mdb/Makefile.am [new file with mode: 0644]
src/murphy-db/mdb/column.c [new file with mode: 0644]
src/murphy-db/mdb/column.h [new file with mode: 0644]
src/murphy-db/mdb/cond.c [new file with mode: 0644]
src/murphy-db/mdb/cond.h [new file with mode: 0644]
src/murphy-db/mdb/handle.c [new file with mode: 0644]
src/murphy-db/mdb/hash.c [new file with mode: 0644]
src/murphy-db/mdb/index.c [new file with mode: 0644]
src/murphy-db/mdb/index.h [new file with mode: 0644]
src/murphy-db/mdb/log.c [new file with mode: 0644]
src/murphy-db/mdb/log.h [new file with mode: 0644]
src/murphy-db/mdb/mqi-types.c [new file with mode: 0644]
src/murphy-db/mdb/row.c [new file with mode: 0644]
src/murphy-db/mdb/row.h [new file with mode: 0644]
src/murphy-db/mdb/sequence.c [new file with mode: 0644]
src/murphy-db/mdb/table.c [new file with mode: 0644]
src/murphy-db/mdb/table.h [new file with mode: 0644]
src/murphy-db/mdb/transaction.c [new file with mode: 0644]
src/murphy-db/mdb/transaction.h [new file with mode: 0644]
src/murphy-db/mqi/Makefile.am [new file with mode: 0644]
src/murphy-db/mqi/db.h [new file with mode: 0644]
src/murphy-db/mqi/mdb-backend.c [new file with mode: 0644]
src/murphy-db/mqi/mdb-backend.h [new file with mode: 0644]
src/murphy-db/mqi/mqi.c [new file with mode: 0644]
src/murphy-db/mql/Makefile.am [new file with mode: 0644]
src/murphy-db/mql/mql-parser.y [new file with mode: 0644]
src/murphy-db/mql/mql-scanner.l [new file with mode: 0644]
src/murphy-db/mql/result.c [new file with mode: 0644]
src/murphy-db/mql/statement.c [new file with mode: 0644]
src/murphy-db/tests/Makefile.am [new file with mode: 0644]
src/murphy-db/tests/check-libmdb.c [new file with mode: 0644]
src/murphy-db/tests/check-libmqi.c [new file with mode: 0644]
src/murphy-db/tests/check-libmql.c [new file with mode: 0644]

index a3cbc6f..f9e6da6 100644 (file)
@@ -37,6 +37,16 @@ AC_PROG_AWK
 AC_PROG_INSTALL
 AM_PROG_CC_C_O
 AM_PROG_LIBTOOL
+AC_PROG_LEX
+AC_PROG_YACC
+
+if test "$LEX" != "flex" ; then
+   AC_MSG_ERROR([flex is required])
+fi
+
+if test "$YACC" != "bison -y" ; then
+   AC_MSG_ERROR([bison is required])
+fi
 
 # Checks for libraries.
 AC_CHECK_LIB([dl], [dlopen dlclose dlsym dlerror])
@@ -82,6 +92,7 @@ AC_SUBST(DBUS_LIBS)
 DBUS_SESSION_DIR="`pkg-config --variable session_bus_services_dir dbus-1`"
 AC_SUBST(DBUS_SESSION_DIR)
 
+
 # Check and enable extra compiler warnings if they are supported.
 AC_ARG_ENABLE(extra-warnings,
               [  --enable-extra-warnings         enable extra compiler warnings],
@@ -173,6 +184,9 @@ PKG_CHECK_MODULES(CHECK,
                   [has_check="yes"], [has_check="no"])
 AM_CONDITIONAL(HAVE_CHECK, test "x$has_check" = "xyes")
 
+AC_SUBST(CHECK_CFLAGS)
+AC_SUBST(CHECK_LIBS)
+
 if test "x$has_check" = "xno"; then
     AC_MSG_WARN([Check framework not found, unit tests are DISABLED.])
 fi
@@ -196,6 +210,12 @@ AC_CONFIG_FILES([build-aux/shave
                 src/daemon/tests/Makefile
                 src/common/murphy-common.pc
                 src/core/murphy-core.pc
+                 src/murphy-db/Makefile
+                 src/murphy-db/mdb/Makefile
+                 src/murphy-db/mqi/Makefile
+                 src/murphy-db/mql/Makefile
+                 src/murphy-db/include/Makefile
+                 src/murphy-db/tests/Makefile
                 doc/Makefile
                 ])
 AC_OUTPUT
index a0bde36..bea1a30 100644 (file)
@@ -1,4 +1,4 @@
-SUBDIRS         = . common/tests core/tests daemon/tests
+SUBDIRS         = . murphy-db common/tests core/tests daemon/tests
 AM_CFLAGS       = $(WARNING_CFLAGS) -I$(top_builddir) -DLIBDIR=\"@LIBDIR@\"
 MURPHY_CFLAGS   = 
 pkgconfigdir    = ${libdir}/pkgconfig
diff --git a/src/murphy-db/Makefile.am b/src/murphy-db/Makefile.am
new file mode 100644 (file)
index 0000000..5396bb8
--- /dev/null
@@ -0,0 +1,7 @@
+if HAVE_CHECK
+TESTDIR = tests
+else
+TESTDIR =
+endif
+
+SUBDIRS = mdb mqi mql include $(TESTDIR)
diff --git a/src/murphy-db/include/Makefile.am b/src/murphy-db/include/Makefile.am
new file mode 100644 (file)
index 0000000..50fd0ea
--- /dev/null
@@ -0,0 +1,6 @@
+nobase_include_HEADERS = murphy-db/mqi-types.h  \
+                         murphy-db/mdb.h \
+                         murphy-db/mqi.h \
+                         murphy-db/mql.h \
+                         murphy-db/statement.h \
+                         murphy-db/result.h
diff --git a/src/murphy-db/include/murphy-db/assert.h b/src/murphy-db/include/murphy-db/assert.h
new file mode 100644 (file)
index 0000000..eb4f348
--- /dev/null
@@ -0,0 +1,17 @@
+#ifndef __MDB_ASSERT_H__
+#define __MDB_ASSERT_H__
+
+
+#define MDB_ASSERT(cond, errcode, retval) \
+    do {                                  \
+        if (!(cond)) {                    \
+            errno = errcode;              \
+            return retval;                \
+        }                                 \
+    } while(0)
+
+#define MDB_CHECKARG(cond, retval)      MDB_ASSERT(cond, EINVAL, retval)
+#define MDB_PREREQUISITE(cond, retval)  MDB_ASSERT(cond, EIO, retval)
+
+
+#endif  /* __MDB_ASSERT_H__ */
diff --git a/src/murphy-db/include/murphy-db/handle.h b/src/murphy-db/include/murphy-db/handle.h
new file mode 100644 (file)
index 0000000..84a2a86
--- /dev/null
@@ -0,0 +1,31 @@
+#ifndef __MDB_HANDLE_H__
+#define __MDB_HANDLE_H__
+
+#include <stdint.h>
+
+#define MDB_HANDLE_INVALID (~((mdb_handle_t)0))
+
+#define MDB_HANDLE_MAP_CREATE  mdb_handle_map_create
+#define MDB_HANDLE_MAP_DESTROY mdb_handle_map_destroy
+
+typedef uint32_t  mdb_handle_t;
+typedef struct mdb_handle_map_s   mdb_handle_map_t;
+
+mdb_handle_map_t *mdb_handle_map_create(void);
+int mdb_handle_map_destroy(mdb_handle_map_t *);
+
+mdb_handle_t mdb_handle_add(mdb_handle_map_t *, void *);
+void *mdb_handle_delete(mdb_handle_map_t *, mdb_handle_t);
+void *mdb_handle_get_data(mdb_handle_map_t *, mdb_handle_t);
+int mdb_handle_print(mdb_handle_map_t *, char *, int);
+
+
+#endif /* __MDB_HANDLE_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/include/murphy-db/hash.h b/src/murphy-db/include/murphy-db/hash.h
new file mode 100644 (file)
index 0000000..8be21b3
--- /dev/null
@@ -0,0 +1,62 @@
+#ifndef __MDB_HASH_H__
+#define __MDB_HASH_H__
+
+#include <murphy-db/mqi-types.h>
+#include <murphy-db/list.h>
+
+
+#define MDB_HASH_TABLE_CREATE(type, max_entries)         \
+    mdb_hash_table_create(max_entries,                   \
+                          mdb_hash_function_##type,      \
+                          mqi_data_compare_##type,       \
+                          mqi_data_print_##type)
+
+#define MDB_HASH_TABLE_DESTROY(h)                        \
+    mdb_hash_table_destroy(h)
+
+#define MDB_HASH_TABLE_FOR_EACH_WITH_KEY(htbl, data, key, cursor)       \
+    for (cursor = NULL;                                                 \
+        (data = mdb_hash_table_iterate(htbl, (void **)&key, &cursor)); )
+#define MDB_HASH_TABLE_FOR_EACH_WITH_KEY_SAFE(htbl, data, key, cursor)  \
+    MDB_HASH_TABLE_FOR_EACH_WITH_KEY(htbl, data, key, cursor)
+
+#define MDB_HASH_TABLE_FOR_EACH(htbl, data, cursor)                     \
+    for (cursor = NULL;  (data = mdb_hash_table_iterate(htbl, NULL, &cursor));)
+#define MDB_HASH_TABLE_FOR_EACH_SAFE(htbl, data, cursor)                \
+    MDB_HASH_TABLE_FOR_EACH(htbl, data, key, cursor)
+
+typedef struct mdb_hash_s mdb_hash_t;
+
+typedef int  (*mdb_hash_function_t)(int, int, int, void *);
+typedef int  (*mdb_hash_compare_t)(int, void *, void *);
+typedef int  (*mdb_hash_print_t)(void *, char *, int);
+
+
+mdb_hash_t *mdb_hash_table_create(int, mdb_hash_function_t, mdb_hash_compare_t,
+                                  mdb_hash_print_t);
+int mdb_hash_table_destroy(mdb_hash_t *);
+int mdb_hash_table_reset(mdb_hash_t *);
+void *mdb_hash_table_iterate(mdb_hash_t *, void **, void **);
+int mdb_hash_table_print(mdb_hash_t *, char *, int);
+
+int mdb_hash_add(mdb_hash_t *, int, void *, void *);
+void *mdb_hash_delete(mdb_hash_t *, int, void *);
+void *mdb_hash_get_data(mdb_hash_t *, int, void *);
+
+int mdb_hash_function_integer(int, int, int, void *);
+int mdb_hash_function_unsignd(int, int, int, void *);
+int mdb_hash_function_string(int, int, int, void *);
+int mdb_hash_function_pointer(int, int, int, void *);
+int mdb_hash_function_varchar(int, int, int, void *);
+int mdb_hash_function_blob(int, int, int, void *);
+
+
+#endif /* __MDB_HASH_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/include/murphy-db/list.h b/src/murphy-db/include/murphy-db/list.h
new file mode 100644 (file)
index 0000000..578b7a6
--- /dev/null
@@ -0,0 +1,93 @@
+#ifndef __MDB_LIST_H__
+#define __MDB_LIST_H__
+
+#include <murphy-db/mqi-types.h>
+
+#define MDB_LIST_RELOCATE(structure, member, ptr)                       \
+    ((structure *)((void *)ptr - MQI_OFFSET(structure, member)))
+
+
+#define MDB_DLIST_HEAD(name)   mdb_dlist_t name = { &(name), &(name) }
+
+#define MDB_DLIST_INIT(self)                                            \
+    do {                                                                \
+        (&(self))->prev = &(self);                                      \
+        (&(self))->next = &(self);                                      \
+    } while(0)
+
+#define MDB_DLIST_EMPTY(name)  ((&(name))->next == &(name))
+
+#define MDB_DLIST_FOR_EACH(structure, member, pos, head)                \
+    for (pos = MDB_LIST_RELOCATE(structure, member, (head)->next);      \
+         &pos->member != (head);                                        \
+         pos = MDB_LIST_RELOCATE(structure, member, pos->member.next))
+
+#define MDB_DLIST_FOR_EACH_SAFE(structure, member, pos, n, head)        \
+    for (pos = MDB_LIST_RELOCATE(structure, member, (head)->next),      \
+           n = MDB_LIST_RELOCATE(structure, member, pos->member.next);  \
+         &pos->member != (head);                                        \
+         pos = n,                                                       \
+           n = MDB_LIST_RELOCATE(structure, member, pos->member.next))
+
+#define MDB_DLIST_FOR_EACH_NOHEAD(structure, member, pos, start)        \
+    for (pos = start;                                                   \
+         &(pos)->member != &(start)->member;                            \
+         pos = MDB_LIST_RELOCATE(structure, member, pos->member.next))
+
+#define MDB_DLIST_FOR_EACH_NOHEAD_SAFE(structure, member, pos,n, start) \
+    for (pos = start,                                                   \
+           n = MDB_LIST_RELOCATE(structure, member, pos->member.next);  \
+         &pos->member != &(start)->member;                              \
+         pos = n,                                                       \
+           n = MDB_LIST_RELOCATE(structure, member, pos->member.next))
+
+#define MDB_DLIST_INSERT_BEFORE(structure, member, new, before)         \
+    do {                                                                \
+        mdb_dlist_t *after = (before)->prev;                            \
+        after->next = &(new)->member;                                   \
+        (new)->member.next = before;                                    \
+        (before)->prev = &(new)->member;                                \
+        (new)->member.prev = after;                                     \
+    } while(0)
+#define MDB_DLIST_APPEND(structure, member, new, head)                  \
+    MDB_DLIST_INSERT_BEFORE(structure, member, new, head)
+
+#define MDB_DLIST_INSERT_AFTER(structure, member, new, after)           \
+    do {                                                                \
+        mdb_dlist_t *before = (after)->next;                            \
+        (after)->next = &((new)->member);                               \
+        (new)->member.next = before;                                    \
+        before->prev = &((new)->member);                                \
+        (new)->member.prev = after;                                     \
+    } while(0)
+#define MDB_DLIST_PREPEND(structure, member, new, head)                 \
+    MDB_DLIST_INSERT_AFTER(structure, member, new, head)
+
+
+#define MDB_DLIST_UNLINK(structure, member, elem)                       \
+    do {                                                                \
+        mdb_dlist_t *after  = (elem)->member.prev;                      \
+        mdb_dlist_t *before = (elem)->member.next;                      \
+        after->next = before;                                           \
+        before->prev = after;                                           \
+        (elem)->member.prev = (elem)->member.next = &(elem)->member;    \
+    } while(0)
+
+
+typedef struct mdb_dlist_s {
+    struct mdb_dlist_s *prev;
+    struct mdb_dlist_s *next;
+} mdb_dlist_t;
+
+
+
+
+#endif /* __MDB_LIST_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/include/murphy-db/mdb.h b/src/murphy-db/include/murphy-db/mdb.h
new file mode 100644 (file)
index 0000000..f124413
--- /dev/null
@@ -0,0 +1,38 @@
+#ifndef __MDB_MDB_H__
+#define __MDB_MDB_H__
+
+#include <murphy-db/mqi-types.h>
+
+typedef struct mdb_table_s mdb_table_t;
+
+mdb_table_t *mdb_table_create(char *, char **, mqi_column_def_t *);
+int mdb_table_drop(mdb_table_t *);
+int mdb_table_create_index(mdb_table_t *, char **);
+int mdb_table_describe(mdb_table_t *, mqi_column_def_t *, int);
+int mdb_table_insert(mdb_table_t *, int, mqi_column_desc_t *, void **);
+int mdb_table_select(mdb_table_t *, mqi_cond_entry_t *,
+                     mqi_column_desc_t *, void *, int, int);
+int mdb_table_select_by_index(mdb_table_t *, mqi_variable_t *,
+                              mqi_column_desc_t *, void *);
+int mdb_table_update(mdb_table_t *, mqi_cond_entry_t *,
+                     mqi_column_desc_t *, void *);
+int mdb_table_delete(mdb_table_t *, mqi_cond_entry_t *);
+
+mdb_table_t *mdb_table_find(char *);
+int mdb_table_get_column_index(mdb_table_t *, char *);
+int mdb_table_get_size(mdb_table_t *);
+char *mdb_table_get_column_name(mdb_table_t *, int);
+mqi_data_type_t mdb_table_get_column_type(mdb_table_t *, int);
+int mdb_table_get_column_size(mdb_table_t *, int);
+int mdb_table_print_rows(mdb_table_t *, char *, int);
+
+
+#endif /* __MDB_MDB_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/include/murphy-db/mqi-types.h b/src/murphy-db/include/murphy-db/mqi-types.h
new file mode 100644 (file)
index 0000000..4318901
--- /dev/null
@@ -0,0 +1,122 @@
+#ifndef __MQI_TYPES_H__
+#define __MQI_TYPES_H__
+
+#include <stdint.h>
+
+#define MQI_QUERY_RESULT_MAX  8192
+#define MQI_COLUMN_MAX        64
+#define MQI_COND_MAX          64
+#define MQL_PARAMETER_MAX     16
+#define MQI_TXDEPTH_MAX       16
+
+#define MQI_DIMENSION(array)  \
+    (sizeof(array) / sizeof(array[0]))
+
+#define MQI_OFFSET(structure, member)  \
+    ((int)((void *)((&((structure *)0)->member)) - (void *)0))
+
+#define MQL_BIND_INDEX_BITS   8
+#define MQL_BIND_INDEX_MAX    (1 << MQL_BIND_INDEX_BITS)
+#define MQL_BIND_INDEX_MASK   (MQL_BIND_INDEX_MAX - 1)
+
+#define MQL_BINDABLE           (1 << (MQL_BIND_INDEX_BITS + 0))
+#define MQL_BIND_INDEX(v)      ((v) & MQL_BIND_INDEX_MASK)
+
+#define MQI_COLUMN_KEY          (1UL << 0)
+#define MQI_COLUMN_AUTOINCR     (1UL << 1)
+
+
+typedef enum {
+    mqi_error = -1,    /* not a data type; used to return error conditions */
+    mqi_unknown = 0,
+    mqi_varchar,
+    mqi_string = mqi_varchar,
+    mqi_integer,
+    mqi_unsignd,
+    mqi_floating,
+    mqi_blob,
+} mqi_data_type_t;
+
+typedef struct {
+    const char      *name;
+    mqi_data_type_t  type;
+    int              length;
+    uint32_t         flags;
+} mqi_column_def_t;
+
+typedef struct {
+    int     cindex;              /* column index */
+    int     offset;              /* offset within the data struct */
+} mqi_column_desc_t;
+
+typedef enum {
+    mqi_done = 0,
+    mqi_end  = mqi_done,
+    mqi_begin,                  /* expression start */
+    mqi_and,
+    mqi_or,
+    mqi_less,
+    mqi_leq,
+    mqi_eq,
+    mqi_geq,
+    mqi_gt,
+    mqi_not,
+    mqi_operator_max
+} mqi_operator_t;
+
+
+typedef struct {
+    mqi_data_type_t  type;
+    uint32_t         flags;
+    union {
+        char       **varchar;
+        int32_t     *integer;
+        uint32_t    *unsignd;
+        double      *floating;
+        void       **blob;
+        void        *generic;
+    };
+} mqi_variable_t;
+
+typedef enum {
+    mqi_operator,
+    mqi_variable,
+    mqi_column
+} mqi_cond_entry_type_t;
+
+typedef struct {
+    mqi_cond_entry_type_t  type;
+    union {
+        mqi_operator_t     operator;
+        mqi_variable_t     variable;
+        int                column;     /* column index actually */
+    };
+} mqi_cond_entry_t;
+
+
+const char *mqi_data_type_str(mqi_data_type_t);
+
+int mqi_data_compare_integer(int, void *, void *);
+int mqi_data_compare_unsignd(int, void *, void *);
+int mqi_data_compare_string(int, void *, void *);
+int mqi_data_compare_pointer(int, void *, void *);
+int mqi_data_compare_varchar(int, void *, void *);
+int mqi_data_compare_blob(int, void *, void *);
+
+int mqi_data_print_integer(void *, char *, int);
+int mqi_data_print_unsignd(void *, char *, int);
+int mqi_data_print_string(void *, char *, int);
+int mqi_data_print_pointer(void *, char *, int);
+int mqi_data_print_varchar(void *, char *, int);
+int mqi_data_print_blob(void *, char *, int);
+
+
+#endif /* __MQI_TYPES_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/include/murphy-db/mqi.h b/src/murphy-db/include/murphy-db/mqi.h
new file mode 100644 (file)
index 0000000..cc98524
--- /dev/null
@@ -0,0 +1,171 @@
+#ifndef __MQI_MQI_H__
+#define __MQI_MQI_H__
+
+#include <murphy-db/mqi-types.h>
+
+#define MQI_HANDLE_INVALID    (~((mqi_handle_t)0))
+
+#define MQI_ALL               NULL
+#define MQI_NO_INDEX          NULL
+
+/* table flags */
+#define MQI_PERSISTENT        (1 << 0)
+#define MQI_TEMPORARY         (1 << 1)
+#define MQI_ANY               (MQI_PERSISTENT | MQI_TEMPORARY)
+#define MQI_TABLE_TYPE_MASK   (MQI_PERSISTENT | MQI_TEMPORARY)
+
+
+#define MQI_COLUMN_DEFINITION(name, type...)  \
+    {name, type}
+
+#define MQI_COLUMN_SELECTOR(column_index, result_structure, result_member) \
+    {column_index, MQI_OFFSET(result_structure, result_member)}
+
+#define MQI_VARCHAR(s)    mqi_varchar, s
+#define MQI_INTEGER       mqi_integer, 0
+#define MQI_UNSIGNED      mqi_unsignd, 0
+#define MQI_BLOB(s)       mqi_blob,    s
+
+#define MQI_COLUMN(column_index)  \
+    {.type=mqi_column, .column=column_index}
+
+#define MQI_VALUE(typ, val)  \
+    {.type=mqi_##typ, .typ=val}
+
+#define MQI_VARIABLE(typ, val) \
+    {.type=mqi_variable, .variable=MQI_VALUE(typ, val)}
+
+#define MQI_OPERATOR(op) \
+    {.type=mqi_operator, .operator=mqi_##op}
+
+
+#define MQI_EXPRESSION(seq)        MQI_OPERATOR(begin), seq, MQI_OPERATOR(end),
+
+
+#define MQI_STRING_VAL(val)        MQI_VALUE(varchar, (char **)&val),
+#define MQI_INTEGER_VAL(val)       MQI_VALUE(integer, (int32_t *)&val),
+#define MQI_UNSIGNED_VAL(val)      MQI_VALUE(unsignd, (uint32_t *)&val),
+#define MQI_BLOB_VAL(val)          MQI_VALUE(blob,    (void **)&val),
+
+#define MQI_STRING_VAR(val)        MQI_VARIABLE(varchar, (char **)&val)
+#define MQI_INTEGER_VAR(val)       MQI_VARIABLE(integer, (int32_t *)&val)
+#define MQI_UNSIGNED_VAR(val)      MQI_VARIABLE(unsignd, (uint32_t *)&val)
+#define MQI_BLOB_VAR(val)          MQI_VARIABLE(blob,    (void **)&val)
+
+
+#define MQI_AND                    MQI_OPERATOR(and),
+#define MQI_OR                     MQI_OPERATOR(or),
+
+#define MQI_LESS(a,b)              a, MQI_OPERATOR(less), b,
+#define MQI_LESS_OR_EQUAL(a,b)     a, MQI_OPERATOR(leq),  b,
+#define MQI_EQUAL(a,b)             a, MQI_OPERATOR(eq),   b,
+#define MQI_GREATER_OR_EQUAL(a,b)  a, MQI_OPERATOR(geq),  b,
+#define MQI_GREATER(a,b)           a, MQI_OPERATOR(gt),   b,
+
+#define MQI_NOT(val)               MQI_OPERATOR(not), val,
+
+#define MQI_COLUMN_DEFINITION_LIST(name, columns...)            \
+    static mqi_column_def_t name[] = {                          \
+        columns,                                                \
+        {NULL, mqi_unknown, 0}                                  \
+    }
+
+#define MQI_INDEX_COLUMN(column_name)  column_name,
+
+#define MQI_INDEX_DEFINITION(name, column_names...)             \
+    static char *name[] = {                                     \
+        column_names                                            \
+        NULL                                                    \
+    }
+
+#define MQI_INDEX_VALUE(name, varlist...)                       \
+    static mqi_variable_t name[] = {varlist}
+
+#define MQI_COLUMN_SELECTION_LIST(name, columns...)             \
+    static mqi_column_desc_t name[] = {                         \
+        columns,                                                \
+        {-1, 1}                                                 \
+    }
+#define MQI_WHERE_CLAUSE(name, seq...)                          \
+    static mqi_cond_entry_t  name[] = {                         \
+        seq                                                     \
+        MQI_OPERATOR(end)                                       \
+    }
+
+#define MQI_BEGIN                                               \
+    mqi_begin_transaction();
+
+#define MQI_COMMIT(id)                                          \
+    mqi_commit_transaction(id)
+
+#define MQI_ROLLBACK(id)                                        \
+    mqi_rollback_transaction(id)
+
+#define MQI_CREATE_TABLE(name, type, column_defs, index_def)    \
+    mqi_create_table(name, type, index_def, column_defs)
+
+#define MQI_DESCRIBE(table, coldefs)                            \
+    mqi_describe(table, coldefs, MQI_DIMENSION(coldefs))
+
+#define MQI_INSERT_INTO(table, column_descs, data)              \
+    mqi_insert_into(table, 0, column_descs, (void **)data)
+
+#define MQI_REPLACE(table, column_descs, data)                  \
+    mqi_insert_into(table, 1, column_descs, (void **)data)
+
+#define MQI_SELECT(columns, table, where, result)               \
+    mqi_select(table, where, columns, result,                   \
+               sizeof(result[0]), MQI_DIMENSION(result))
+
+#define MQI_SELECT_BY_INDEX(columns, table, idxvars, result)    \
+    mqi_select_by_index(table, idxvars, columns, result)
+
+#define MQI_UPDATE(table, column_descs, data, where)            \
+    mqi_update(table, where, column_descs, data)
+
+#define MQI_DELETE(table, where)                                \
+    mqi_delete_from(table, where)
+
+
+typedef uint32_t mqi_handle_t;
+
+
+int mqi_open(void);
+int mqi_close(void);
+
+int mqi_show_tables(uint32_t, char **, int);
+
+mqi_handle_t mqi_begin_transaction(void);
+int mqi_commit_transaction(mqi_handle_t);
+int mqi_rollback_transaction(mqi_handle_t);
+mqi_handle_t mqi_get_transaction_handle(void);
+mqi_handle_t mqi_create_table(char *, uint32_t, char **, mqi_column_def_t *);
+int mqi_create_index(mqi_handle_t, char **);
+int mqi_drop_table(mqi_handle_t);
+int mqi_describe(mqi_handle_t, mqi_column_def_t *, int);
+int mqi_insert_into(mqi_handle_t, int, mqi_column_desc_t *, void **);
+int mqi_delete_from(mqi_handle_t, mqi_cond_entry_t *);
+int mqi_update(mqi_handle_t, mqi_cond_entry_t *, mqi_column_desc_t *, void *);
+int mqi_select(mqi_handle_t, mqi_cond_entry_t *, mqi_column_desc_t *,
+               void *, int, int);
+int mqi_select_by_index(mqi_handle_t, mqi_variable_t *,
+                        mqi_column_desc_t *, void *);
+
+mqi_handle_t mqi_get_table_handle(char *);
+int mqi_get_column_index(mqi_handle_t, char *);
+int mqi_get_table_size(mqi_handle_t);
+char *mqi_get_column_name(mqi_handle_t, int);
+mqi_data_type_t mqi_get_column_type(mqi_handle_t, int);
+int mqi_get_column_size(mqi_handle_t, int);
+int mqi_print_rows(mqi_handle_t, char *, int);
+
+
+#endif /* __MQI_MQI_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/include/murphy-db/mql.h b/src/murphy-db/include/murphy-db/mql.h
new file mode 100644 (file)
index 0000000..11b338b
--- /dev/null
@@ -0,0 +1,19 @@
+#ifndef __MQL_MQL_H__
+#define __MQL_MQL_H__
+
+#include <murphy-db/statement.h>
+
+int mql_exec_file(const char *);
+mql_result_t *mql_exec_string(mql_result_type_t, const char *);
+mql_statement_t *mql_precompile(const char *);
+
+
+#endif  /* __MQL_MQL_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/include/murphy-db/result.h b/src/murphy-db/include/murphy-db/result.h
new file mode 100644 (file)
index 0000000..7fce13c
--- /dev/null
@@ -0,0 +1,57 @@
+#ifndef __MQL_RESULT_H__
+#define __MQL_RESULT_H__
+
+#include <murphy-db/mqi-types.h>
+
+typedef enum {
+    mql_result_error = -1,
+    mql_result_unknown = 0,
+    mql_result_dontcare = mql_result_unknown,
+    mql_result_columns,
+    mql_result_rows,
+    mql_result_string,
+    mql_result_list,
+} mql_result_type_t;
+
+
+typedef struct mql_result_s {
+    mql_result_type_t  type;
+    uint8_t            data[0];
+} mql_result_t;
+
+int              mql_result_is_success(mql_result_t *);
+
+int              mql_result_error_get_code(mql_result_t *);
+const char      *mql_result_error_get_message(mql_result_t *);
+
+int              mql_result_columns_get_column_count(mql_result_t *);
+const char      *mql_result_columns_get_name(mql_result_t *, int);
+mqi_data_type_t  mql_result_columns_get_type(mql_result_t *, int);
+int              mql_result_columns_get_length(mql_result_t *, int);
+
+int              mql_result_rows_get_row_count(mql_result_t *);
+const char      *mql_result_rows_get_string(mql_result_t*, int,int, char*,int);
+int32_t          mql_result_rows_get_integer(mql_result_t *, int,int);
+uint32_t         mql_result_rows_get_unsigned(mql_result_t *, int,int);
+double           mql_result_rows_get_floating(mql_result_t *, int,int);
+
+const char      *mql_result_string_get(mql_result_t *);
+
+int              mql_result_list_get_length(mql_result_t *);
+const char      *mql_result_list_get_string(mql_result_t *, int, char *, int);
+int32_t          mql_result_list_get_integer(mql_result_t *, int);
+int32_t          mql_result_list_get_unsigned(mql_result_t *, int);
+double           mql_result_list_get_floating(mql_result_t *, int);
+
+void             mql_result_free(mql_result_t *);
+
+
+#endif /* __MQL_RESULT_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/include/murphy-db/sequence.h b/src/murphy-db/include/murphy-db/sequence.h
new file mode 100644 (file)
index 0000000..02f5584
--- /dev/null
@@ -0,0 +1,47 @@
+#ifndef __MDB_SEQUENCE_H__
+#define __MDB_SEQUENCE_H__
+
+#include <murphy-db/mqi-types.h>
+
+
+#define MDB_SEQUENCE_TABLE_CREATE(type, alloc)              \
+    mdb_sequence_table_create(alloc,                        \
+                              mqi_data_compare_##type,      \
+                              mqi_data_print_##type)
+
+#define MDB_SEQUENCE_FOR_EACH(seq, data, cursor)            \
+    for (cursor = NULL;  (data = mdb_sequence_iterate(seq, &cursor)); )
+
+#define MDB_SEQUENCE_FOR_EACH_SAFE(seq, data, cursor)       \
+    MDB_SEQUENCE_FOR_EACH(seq, data, cursor)
+
+
+typedef struct mdb_sequence_s mdb_sequence_t;
+
+typedef int  (*mdb_sequence_compare_t)(int, void *, void *);
+typedef int  (*mdb_sequence_print_t)(void *, char *, int);
+
+
+mdb_sequence_t *mdb_sequence_table_create(int, mdb_sequence_compare_t,
+                                          mdb_sequence_print_t);
+int mdb_sequence_table_destroy(mdb_sequence_t *);
+int mdb_sequence_table_get_size(mdb_sequence_t *);
+int mdb_sequence_table_reset(mdb_sequence_t *);
+int mdb_sequence_table_print(mdb_sequence_t *, char *, int);
+
+int mdb_sequence_add(mdb_sequence_t *, int, void *, void *);
+void *mdb_sequence_delete(mdb_sequence_t *, int, void *);
+void *mdb_sequence_iterate(mdb_sequence_t *, void **);
+void mdb_sequence_cursor_destroy(mdb_sequence_t *, void **);
+
+
+
+#endif /* __MDB_SEQUENCE_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/include/murphy-db/statement.h b/src/murphy-db/include/murphy-db/statement.h
new file mode 100644 (file)
index 0000000..5f9e3a8
--- /dev/null
@@ -0,0 +1,46 @@
+#ifndef __MQL_STATEMENT_H__
+#define __MQL_STATEMENT_H__
+
+#include <murphy-db/result.h>
+
+typedef struct mql_result_s mql_result_t;
+
+typedef enum {
+    mql_statement_unknown = 0,
+    mql_statement_show_tables,
+    mql_statement_describe,
+    mql_statement_create_table,
+    mql_statement_create_index,
+    mql_statement_drop_table,
+    mql_statement_drop_index,
+    mql_statement_begin,
+    mql_statement_commit,
+    mql_statement_rollback,
+    mql_statement_insert,
+    mql_statement_update,
+    mql_statement_delete,
+    mql_statement_select,
+    /* do not add anything after this */
+    mql_statement_last
+} mql_statement_type_t;
+
+typedef struct mql_statement_s {
+    mql_statement_type_t  type;
+    uint8_t               data[0];
+} mql_statement_t;
+
+
+mql_result_t *mql_exec_statement(mql_result_type_t, mql_statement_t *);
+int mql_bind_value(mql_statement_t *, int, mqi_data_type_t, ...);
+void mql_statement_free(mql_statement_t *);
+
+
+#endif /* __MQL_STATEMENT_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/Makefile.am b/src/murphy-db/mdb/Makefile.am
new file mode 100644 (file)
index 0000000..2c513aa
--- /dev/null
@@ -0,0 +1,19 @@
+pkglib_LTLIBRARIES = libmdb.la
+
+libmdb_la_CFLAGS = -I../include
+
+libmdb_la_SOURCES = ../include/murphy-db/assert.h \
+                    ../include/murphy-db/list.h \
+                    ../include/murphy-db/handle.h \
+                    ../include/murphy-db/hash.h \
+                    ../include/murphy-db/sequence.h \
+                    ../include/murphy-db/mqi-types.h \
+                    ../include/murphy-db/mdb.h \
+                    list.h handle.c hash.c sequence.c mqi-types.c \
+                    column.h column.c \
+                    cond.h cond.c \
+                    index.h index.c \
+                    log.h log.c \
+                    row.h row.c \
+                    table.h table.c \
+                    transaction.h transaction.c
diff --git a/src/murphy-db/mdb/column.c b/src/murphy-db/mdb/column.c
new file mode 100644 (file)
index 0000000..1849f7e
--- /dev/null
@@ -0,0 +1,156 @@
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdio.h>
+#include <errno.h>
+
+#define _GNU_SOURCE
+#include <string.h>
+
+#include <murphy-db/list.h>
+#include <murphy-db/handle.h>
+#include <murphy-db/hash.h>
+#include <murphy-db/sequence.h>
+#include "column.h"
+#include "index.h"
+#include "table.h"
+
+static int print_blob(uint8_t *, int data, char *, int);
+
+void mdb_column_write(mdb_column_t      *dst_desc, void *dst_data,
+                      mqi_column_desc_t *src_desc, void *src_data)
+{
+    int lgh;
+    void *dst, *src;
+
+    if (dst_desc && dst_data && src_desc && src_desc->offset >= 0 && src_data){
+        dst = dst_data + dst_desc->offset;
+        src = src_data + src_desc->offset;
+        lgh = dst_desc->length;
+
+        switch (dst_desc->type) {
+
+        case mqi_varchar:
+            memset(dst, 0, lgh);
+            strncpy((char *)dst, *(const char **)src, lgh-1);
+            break;
+            
+        case mqi_integer:
+            *(int32_t *)dst = *(int32_t *)src;
+            break;
+            
+        case mqi_unsignd:
+            *(uint32_t *)dst = *(uint32_t *)src;
+            break;
+            
+        case mqi_blob:
+            memcpy(dst, src, lgh);
+            break;
+            
+        default:
+            /* we do not knopw what this is,
+               so we silently ignore it */
+            break;
+        }
+    }
+}
+
+
+void mdb_column_read(mqi_column_desc_t *dst_desc, void *dst_data,
+                     mdb_column_t      *src_desc, void *src_data)
+{
+    int lgh;
+    void *dst, *src;
+
+    if (dst_desc && dst_data && src_desc && src_data) {
+        dst = dst_data + dst_desc->offset;
+        src = src_data + src_desc->offset;
+        lgh = src_desc->length;
+
+        switch (src_desc->type) {
+
+        case mqi_varchar:
+            *(char **)dst = (char *)src;
+            break;
+            
+        case mqi_integer:
+            *(int32_t *)dst = *(int32_t *)src;
+            break;
+            
+        case mqi_unsignd:
+            *(uint32_t *)dst = *(uint32_t *)src;
+            break;
+            
+        case mqi_blob:
+            memcpy(dst, src, lgh);
+            break;
+            
+        default:
+            /* we do not know what this is,
+               so we silently ignore it */
+            break;
+        }
+    }
+}
+
+
+int mdb_column_print_header(mdb_column_t *cdesc, char *buf, int len)
+{
+    int r;
+    int l;
+
+    if (!cdesc || !buf || len < 1)
+        r = 0;
+    else {
+        switch (cdesc->type) {
+        case mqi_varchar:  l = cdesc->length;                       break;
+        case mqi_integer:  l = 11;                                  break;
+        case mqi_unsignd:  l = 11;                                  break;
+        case mqi_blob:     l = cdesc->length > 0 ? (cdesc->length * 3) - 1 : 0;
+                                                                    break;
+        default:           l = 0;                                   break;
+        }
+
+        r = (l > 0) ? snprintf(buf,len, "%*s", l, cdesc->name) : 0;
+    }
+
+    return r;
+}
+
+
+int mdb_column_print(mdb_column_t *cdesc, void *data, char *buf, int len)
+{
+    int   r;
+    int   l;
+    void *d;
+
+    if (!cdesc || !data || !buf || len < 1)
+        r = 0;
+    else {
+        d = data + cdesc->offset;
+        l = cdesc->length;
+
+        switch (cdesc->type) {
+        case mqi_varchar:  r = snprintf(buf,len, "%*s", l, (char *)d);   break;
+        case mqi_integer:  r = snprintf(buf,len, "%11d", *(int32_t *)d); break;
+        case mqi_unsignd:  r = snprintf(buf,len, " %10u",*(uint32_t*)d); break;
+        case mqi_blob:     r = print_blob(d,cdesc->length, buf,len);     break;
+        default:           r = 0;                                        break;
+        }
+    }
+
+    return r;
+}
+
+static int print_blob(uint8_t *data, int data_len, char *buf, int buflen)
+{
+    return 0;
+}
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/column.h b/src/murphy-db/mdb/column.h
new file mode 100644 (file)
index 0000000..4251e07
--- /dev/null
@@ -0,0 +1,32 @@
+#ifndef __MDB_COLUMN_H__
+#define __MDB_COLUMN_H__
+
+#include <stdint.h>
+
+#define MDB_COLUMN_LENGTH_MAX   1024
+
+
+#include <murphy-db/mqi-types.h>
+
+typedef struct {
+    char            *name;
+    mqi_data_type_t  type;
+    int              length;
+    int              offset;
+    uint32_t         flags;
+} mdb_column_t;
+
+void mdb_column_write(mdb_column_t *, void *, mqi_column_desc_t *, void *);
+void mdb_column_read(mqi_column_desc_t *, void *, mdb_column_t *, void *);
+int  mdb_column_print_header(mdb_column_t *, char *, int);
+int  mdb_column_print(mdb_column_t *, void *, char *, int);
+
+#endif /* __MDB_COLUMN_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/cond.c b/src/murphy-db/mdb/cond.c
new file mode 100644 (file)
index 0000000..bea74e1
--- /dev/null
@@ -0,0 +1,369 @@
+#include <stdint.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdio.h>
+#include <errno.h>
+
+#define _GNU_SOURCE
+#include <string.h>
+
+#include <murphy-db/assert.h>
+#include <murphy-db/list.h>
+#include <murphy-db/handle.h>
+#include <murphy-db/hash.h>
+#include <murphy-db/sequence.h>
+#include "column.h"
+#include "index.h"
+#include "table.h"
+#include "cond.h"
+
+
+typedef struct {
+    mqi_data_type_t  type;
+    union {
+        char        *varchar;
+        int32_t      integer;
+        uint32_t     unsignd;
+        void        *blob;
+        void        *data;
+    };
+} cond_data_t;
+
+#define PRECEDENCE_DATA 256
+
+typedef struct {
+    int                precedence; /* 256 => data, precedence otherwise */
+    union {
+        cond_data_t    data;
+        mqi_operator_t operator;
+    };
+} cond_stack_t;
+
+static int cond_get_data(cond_stack_t*,mqi_cond_entry_t*,mdb_column_t*,void*);
+static int cond_eval(cond_stack_t *, cond_stack_t *, int);
+static int cond_relop(mqi_operator_t, cond_stack_t *, cond_stack_t *);
+static int cond_binary_logicop(mqi_operator_t, cond_stack_t *,
+                               cond_stack_t *);
+static int cond_unary_logicop(mqi_operator_t, cond_stack_t *);
+
+int mdb_cond_evaluate(mdb_table_t *tbl, mqi_cond_entry_t **cond_ptr,void *data)
+{
+    static int precedence[mqi_operator_max] = {
+        [ mqi_done  ] = 0,
+        [ mqi_begin ] = 1,
+        [ mqi_and   ] = 2,
+        [ mqi_or    ] = 3,
+        [ mqi_less  ] = 4,
+        [ mqi_leq   ] = 4,
+        [ mqi_eq    ] = 4,
+        [ mqi_geq   ] = 4,
+        [ mqi_gt    ] = 4,
+        [ mqi_not   ] = 5
+    };
+
+    mqi_cond_entry_t *cond       = *cond_ptr;
+    cond_stack_t      stack[256] = { [0] = {precedence[mqi_begin],
+                                            .operator = mqi_begin}
+                                   };
+    cond_stack_t     *sp         = stack + 1;
+    cond_stack_t     *lastop     = stack;
+    int               result;
+    int               pr;
+
+    MDB_CHECKARG(cond && data, -1);
+
+    for (;;) {
+        switch (cond->type) {
+
+        case mqi_operator:
+            pr  = precedence[cond->operator];
+            sp += cond_eval(sp, lastop, pr);
+
+            switch (cond->operator) {
+
+            case mqi_begin:
+                cond++;
+                result = mdb_cond_evaluate(tbl, &cond, data);
+                cond++;
+
+                sp->data.integer = result >= 0 ? result : 0;
+                sp->precedence   = PRECEDENCE_DATA;
+                sp->data.type    = mqi_integer;
+                sp++;
+                break;
+                
+            case mqi_end:
+                *cond_ptr = cond+1;
+                sp--;
+                if (sp->precedence < PRECEDENCE_DATA ||
+                    sp->data.type != mqi_integer)
+                {
+                    errno = ENOENT;
+                    return -1;
+                }
+                return sp->data.integer ? 1 : 0;
+
+            case mqi_and:
+            case mqi_or:
+            case mqi_less:
+            case mqi_leq:
+            case mqi_eq:
+            case mqi_geq:
+            case mqi_gt:
+            case mqi_not:
+                lastop = sp++;
+                lastop->precedence = pr;
+                lastop->operator = cond->operator;
+                cond++;
+                break;
+
+            default:
+                break;
+            }
+            break;
+
+        case mqi_variable:
+        case mqi_column:
+            sp += cond_get_data(sp, cond, tbl->columns, data);
+            cond++;
+            break;
+
+        default:
+            errno = EINVAL;
+            return -1;
+        }
+
+    } /* for ;; */
+}
+
+static int cond_get_data(cond_stack_t     *sp,
+                         mqi_cond_entry_t *cond,
+                         mdb_column_t     *columns,
+                         void             *data)
+{
+    mqi_column_desc_t  sp_desc[2];
+    cond_data_t       *sd;
+    mdb_column_t      *col_desc;
+    mqi_variable_t    *var;
+    int                ok;
+
+    switch (cond->type) {
+
+    case mqi_variable:
+        sd  = &sp->data;
+        var = &cond->variable;
+
+        if (!var->generic)
+            ok = 0;
+        else {
+            switch ((sp->data.type = var->type)) {
+            case mqi_varchar:   sd->varchar = *var->varchar;  ok = 1;   break;
+            case mqi_integer:   sd->integer = *var->integer;  ok = 1;   break;
+            case mqi_unsignd:   sd->unsignd = *var->unsignd;  ok = 1;   break;
+            case mqi_blob:      sd->blob    = *var->blob;     ok = 1;   break;
+            default:                                          ok = 0;   break;
+            }
+        }
+        break;
+
+    case mqi_column: {
+        col_desc = columns + cond->column;
+        sp_desc[0].cindex  = cond->column;
+        sp_desc[0].offset = 0;
+        sp_desc[1].cindex  = -1;
+        sp_desc[1].offset = -1;
+        mdb_column_read(sp_desc, &sp->data.data, col_desc, data);
+        sp->data.type = col_desc->type;
+        ok = 1;
+        }
+        break;
+
+    default:
+        ok = 0;
+        break;
+    }
+
+    sp->precedence = PRECEDENCE_DATA * ok;
+
+    return ok;
+}
+
+static int cond_eval(cond_stack_t *sp,cond_stack_t *lastop,int new_precedence)
+{
+    cond_stack_t *result;
+    cond_stack_t *newsp;
+    int value;
+    int stack_advance = 0;
+
+    while (new_precedence < lastop->precedence) {
+        switch (lastop->operator) {
+
+        case mqi_begin:
+            /* stack: (0)begin, (1)operand => (0)result */
+            newsp = (result = lastop) + 1;
+            value = (lastop+1)->data.integer;
+            new_precedence = INT_MAX;
+            goto store_on_stack;
+
+        case mqi_and:
+        case mqi_or:
+            /* stack: (-1)operand1, (0)operator, (1)operand2 => (-1)result */
+            result = (newsp = lastop) - 1;
+            value = cond_binary_logicop(lastop->operator, lastop-1,lastop+1);
+            goto find_new_lastop_and_store_on_stack;
+            
+        case mqi_less:
+        case mqi_leq:
+        case mqi_eq:
+        case mqi_geq:
+        case mqi_gt:
+            /* stack: (-1)operand1, (0)operator, (1)operand2 => (-1)result */
+            result = (newsp = lastop) - 1;
+            value = cond_relop(lastop->operator, lastop-1,lastop+1);
+            goto find_new_lastop_and_store_on_stack;
+
+        case mqi_not:
+            /* stack: (0)operator, (1)operand => (0)result */
+            newsp = (result = lastop) + 1;
+            value = cond_unary_logicop(lastop->operator, lastop+1);
+            goto find_new_lastop_and_store_on_stack;
+            
+        find_new_lastop_and_store_on_stack:
+            for (lastop--;  lastop->precedence >= PRECEDENCE_DATA;  lastop--)
+                ;
+            /* intentional fall over */
+            
+        store_on_stack:
+            result->precedence   = PRECEDENCE_DATA;
+            result->data.type    = mqi_integer;
+            result->data.integer = value;
+            /* intentional fall over */
+
+        default:
+            stack_advance = newsp - sp;
+            break;
+        }
+    }
+
+    return stack_advance;
+}
+
+
+static int cond_relop(mqi_operator_t op, cond_stack_t *v1, cond_stack_t *v2)
+{
+    cond_data_t *d1 = &v1->data;
+    cond_data_t *d2 = &v2->data;
+    int cmp;
+
+    if (v1->precedence >= PRECEDENCE_DATA &&
+        v2->precedence >= PRECEDENCE_DATA &&
+        d1->type == d2->type)
+    {
+        switch (d1->type) {
+        case mqi_varchar:
+            if (!d1->varchar && !d2->varchar)
+                cmp = 0;
+            else if (!d1->varchar)
+                cmp = -1;
+            else if (!d2->varchar)
+                cmp = 1;
+            else
+                cmp = strcmp(d1->varchar, d2->varchar);
+            break;
+
+        case mqi_integer:
+            if (d1->integer > d2->integer)
+                cmp = 1;
+            else if (d1->integer == d2->integer)
+                cmp = 0;
+            else
+                cmp = -1;
+            break;
+
+        case mqi_unsignd:
+            if (d1->unsignd > d2->unsignd)
+                cmp = 1;
+            else if (d1->unsignd == d2->unsignd)
+                cmp = 0;
+            else
+                cmp = -1;
+            break;
+
+        default:
+            return 0;
+        }
+
+        switch (op) {
+        case mqi_less:  return cmp <  0;
+        case mqi_leq:   return cmp <= 0;
+        case mqi_eq:    return cmp == 0;
+        case mqi_geq:   return cmp >= 0;
+        case mqi_gt:    return cmp >  0;
+        default:        return 0;
+        }
+    }
+
+    return 0;
+}
+
+static int cond_binary_logicop(mqi_operator_t op,
+                               cond_stack_t  *v1,
+                               cond_stack_t  *v2)
+{
+    cond_data_t *d1 = &v1->data;
+    cond_data_t *d2 = &v2->data;
+
+    if (v1->precedence >= PRECEDENCE_DATA &&
+        v2->precedence >= PRECEDENCE_DATA &&
+        d1->type == d2->type)
+    {
+        switch (op) {
+
+        case mqi_and:
+            switch (d1->type) {
+            case mqi_integer:   return d1->integer && d2->integer;
+            case mqi_unsignd:   return d1->unsignd && d2->unsignd;
+            default:            return 0;
+            }
+            break;
+
+        case mqi_or:
+            switch (d1->type) {
+            case mqi_integer:   return d1->integer || d2->integer;
+            case mqi_unsignd:   return d1->unsignd || d2->unsignd;
+            default:            return 0;
+            }
+            break;
+
+        default:
+            return 0;
+        }
+    }
+
+    return 0;
+}
+
+static int cond_unary_logicop(mqi_operator_t op, cond_stack_t *v)
+{
+    cond_data_t *d = &v->data;
+
+    if (v->precedence >= PRECEDENCE_DATA && op == mqi_not) {
+        switch (d->type) {
+        case mqi_varchar:  return d->varchar && d->varchar[0] ? 0 : 1;
+        case mqi_integer:  return d->integer ? 0 : 1;
+        case mqi_unsignd:  return d->unsignd ? 0 : 1;
+        default:           return 0;
+        }
+    }
+
+    return 0;
+}
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/cond.h b/src/murphy-db/mdb/cond.h
new file mode 100644 (file)
index 0000000..39def18
--- /dev/null
@@ -0,0 +1,19 @@
+#ifndef __MDB_COND_H__
+#define __MDB_COND_H__
+
+#include <murphy-db/mqi-types.h>
+
+typedef struct mdb_table_s  mdb_table_t;
+
+int mdb_cond_evaluate(mdb_table_t *, mqi_cond_entry_t **, void *);
+
+
+#endif /* __MDB_COND_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/handle.c b/src/murphy-db/mdb/handle.c
new file mode 100644 (file)
index 0000000..19b6121
--- /dev/null
@@ -0,0 +1,348 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+
+#define __USE_GNU
+#include <string.h>
+
+#include <murphy-db/assert.h>
+#include <murphy-db/handle.h>
+
+#define HANDLE_INDEX_INVALID -1
+#define HANDLE_USEID_INVALID -1
+
+#define HANDLE_USEID_BITS 16
+#define HANDLE_INDEX_BITS ((sizeof(mdb_handle_t) * 8) - HANDLE_USEID_BITS)
+#define HANDLE_USEID_MAX  (((mdb_handle_t)1) << HANDLE_USEID_BITS)
+#define HANDLE_INDEX_MAX  (((mdb_handle_t)1) << HANDLE_INDEX_BITS)
+#define HANDLE_USEID_MASK (HANDLE_USEID_MAX - 1)
+#define HANDLE_INDEX_MASK (HANDLE_INDEX_MAX - 1)
+
+#define HANDLE_MAKE(useid, index) (                                      \
+    (((mdb_handle_t)(useid) & HANDLE_USEID_MASK) << HANDLE_INDEX_BITS) | \
+    (((mdb_handle_t)(index) & HANDLE_INDEX_MASK))                        \
+)
+
+#define HANDLE_USEID(h) (((h) >> HANDLE_INDEX_BITS) & HANDLE_USEID_MASK)
+#define HANDLE_INDEX(h) ((h) & HANDLE_INDEX_MASK)
+
+
+typedef long long int bucket_t;
+
+
+typedef struct {
+    int       nbucket;
+    bucket_t *buckets;
+} freemap_t;
+
+typedef struct {
+    uint32_t   useid;
+    void      *data;
+} indextbl_entry_t;
+
+
+typedef struct {
+    int               nentry;
+    indextbl_entry_t *entries;
+} indextbl_t;
+
+typedef struct mdb_handle_map_s {
+    freemap_t   freemap;
+    indextbl_t  indextbl;
+} mdb_handle_map_t;
+
+
+static mdb_handle_t index_alloc(indextbl_t *, int, void *);
+static void *index_realloc(indextbl_t *, uint32_t, int, void *);
+static void *index_free(indextbl_t *, uint32_t, int);
+static int freemap_alloc(freemap_t *);
+static int freemap_free(freemap_t *, int);
+
+static bucket_t  empty_bucket    = ~((bucket_t)0);
+static int       bits_per_bucket = sizeof(bucket_t) * 8;
+
+
+mdb_handle_map_t *mdb_handle_map_create(void)
+{
+    mdb_handle_map_t *hmap;
+
+    if (!(hmap = calloc(1, sizeof(mdb_handle_map_t)))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    return hmap;
+}
+
+int mdb_handle_map_destroy(mdb_handle_map_t *hmap)
+{
+    MDB_CHECKARG(hmap, -1);
+
+    free(hmap->freemap.buckets);
+    free(hmap->indextbl.entries);
+
+    free(hmap);
+
+    return 0;
+}
+
+
+mdb_handle_t mdb_handle_add(mdb_handle_map_t *hmap, void *data)
+{
+    int index;
+
+    MDB_CHECKARG(hmap && data, MDB_HANDLE_INVALID);
+    
+    if ((index = freemap_alloc(&hmap->freemap)) == HANDLE_INDEX_INVALID) {
+        return MDB_HANDLE_INVALID;
+    }
+    
+    return index_alloc(&hmap->indextbl, index, data);
+}
+
+void *mdb_handle_delete(mdb_handle_map_t *hmap, mdb_handle_t h)
+{
+    uint32_t  useid = HANDLE_USEID(h);
+    int       index = HANDLE_INDEX(h);
+    void     *old_data;
+
+    MDB_CHECKARG(hmap && h != MDB_HANDLE_INVALID, NULL);
+
+
+    if (!(old_data = index_free(&hmap->indextbl, useid,index))) {
+        /* errno has been set by index_free() */
+        return NULL;
+    }
+
+    if (freemap_free(&hmap->freemap, index) < 0) {
+        /* errno has been set by freemap_free() */
+        return NULL;
+    }
+    
+    return old_data;
+}
+
+void *mdb_handle_get_data(mdb_handle_map_t *hmap, mdb_handle_t h)
+{
+    uint32_t          useid = HANDLE_USEID(h);
+    int               index = HANDLE_INDEX(h);
+    indextbl_t       *indextbl;
+    indextbl_entry_t *entry;
+
+    MDB_CHECKARG(hmap && h != MDB_HANDLE_INVALID, NULL);
+
+    indextbl = &hmap->indextbl;
+
+    if (index >= indextbl->nentry) {
+        errno = EKEYREJECTED;
+        return NULL;
+    }
+
+    entry = indextbl->entries + index;
+
+    if (entry->useid != useid) {
+        errno = ENOANO;
+        return NULL;
+    }
+
+    if (!entry->data)
+        errno = ENODATA;
+
+    return entry->data;
+}
+
+
+int mdb_handle_print(mdb_handle_map_t *hmap, char *buf, int len)
+{
+    indextbl_t *it;
+    char *p, *e;
+    int i;
+
+    MDB_CHECKARG(hmap && buf && len > 0, -1);
+
+    it = &hmap->indextbl;
+    e = (p = buf) + len;
+
+    p += snprintf(p, e-p, "   useid index data\n");
+    
+    for (i = 0;   i < it->nentry && e > p;   i++) {
+        indextbl_entry_t *en = it->entries + i;
+
+        if (en->data)
+            p += snprintf(p, e-p, "   %5u %5d %p\n",en->useid, i, en->data);
+    }
+
+    return p - buf;
+}
+
+
+static mdb_handle_t index_alloc(indextbl_t *indextbl, int index, void *data)
+{
+#define ALIGN(i,a) ((((i) + (a)-1) / a) * a)
+
+    mdb_handle_t handle;
+    int nentry;
+    indextbl_entry_t *entries, *entry;
+    size_t size;
+    
+    MDB_CHECKARG(index >= 0 && index < HANDLE_INDEX_MAX && data,
+                 MDB_HANDLE_INVALID);
+    
+    if (index >= indextbl->nentry) {
+        nentry  = ALIGN(index + 1, bits_per_bucket);
+        size    = sizeof(indextbl_entry_t) * nentry;
+        entries = realloc(indextbl->entries, size);
+        
+        if (!entries) {
+            errno = ENOMEM;
+            return MDB_HANDLE_INVALID;
+        }
+        
+        size = sizeof(indextbl_entry_t) * (nentry - indextbl->nentry);
+        memset(entries + indextbl->nentry, 0, size);
+        
+        indextbl->nentry  = nentry;
+        indextbl->entries = entries;
+    }
+
+    entry = indextbl->entries + index;
+    
+    if (entry->data && entry->data != data) {
+        errno = EBUSY;
+        return MDB_HANDLE_INVALID;
+    }
+    
+    entry->useid += 1;
+    entry->data   = data;
+    
+    handle = HANDLE_MAKE(entry->useid, index);
+    
+    return handle;
+
+#undef ALIGN
+}
+
+
+static void *index_realloc(indextbl_t *indextbl,
+                           uint32_t    useid,
+                           int         index,
+                           void       *data)
+{
+    indextbl_entry_t *entry;
+    void *old_data;
+
+    MDB_CHECKARG(indextbl, NULL);
+    
+    if (index < 0 || index >= indextbl->nentry) {
+        errno = EKEYREJECTED;
+        return NULL;
+    }
+    
+    entry = indextbl->entries + index;
+    
+    if (entry->useid != useid) {
+        errno = ENOKEY;
+        return NULL;
+    }
+
+    if (!(old_data = entry->data)) {
+        errno = ENOENT;
+        return NULL;
+    }
+    
+    
+    entry->data = data;
+    
+    return old_data;
+}
+
+static void *index_free(indextbl_t *indextbl, uint32_t useid, int index)
+{
+    return index_realloc(indextbl, useid,index, NULL);
+}
+
+static int freemap_alloc(freemap_t *freemap)
+{
+    bucket_t  mask;
+    bucket_t *bucket;
+    int       nbucket;
+    bucket_t *buckets;
+    int       bucket_idx;
+    int       bit_idx;
+    int       index;
+    size_t    size;
+    
+    for (bucket_idx = 0;   bucket_idx < freemap->nbucket;   bucket_idx++) {
+        bucket = freemap->buckets + bucket_idx;
+        
+        if (*bucket && (bit_idx = ffsll(*bucket) - 1) >= 0) {
+            index = bucket_idx * bits_per_bucket + bit_idx;
+            mask  = ~(((bucket_t)1) << bit_idx);
+            *bucket &= mask;
+            return index;
+        }
+    }
+    
+    index   = bucket_idx * bits_per_bucket;
+    nbucket = bucket_idx + 1;
+    size    = sizeof(bucket_t) * nbucket;
+    buckets = realloc(freemap->buckets, size);
+    
+    if (!buckets) {
+        errno = ENOMEM;
+        return HANDLE_INDEX_INVALID;
+    }
+    
+    buckets[bucket_idx] = ~((bucket_t)1);
+
+    freemap->nbucket = nbucket;
+    freemap->buckets = buckets;
+    
+    return index;
+}
+
+
+static int freemap_free(freemap_t *freemap, int index)
+{
+    int       bucket_idx = index / bits_per_bucket;
+    int       bit_idx    = index % bits_per_bucket;
+    int       nbucket;
+    bucket_t *buckets;
+    size_t    size;
+    
+    if (freemap && index >= 0 && bucket_idx < freemap->nbucket) {
+        freemap->buckets[bucket_idx] |= ((bucket_t)1) << bit_idx;
+        
+        if ((bucket_idx + 1 == freemap->nbucket) &&
+            freemap->buckets[bucket_idx] == empty_bucket) {
+            if (freemap->nbucket == 1) {
+                free(freemap->buckets);
+                freemap->nbucket = 0;
+                freemap->buckets = NULL;
+            }
+            else {
+                nbucket = bucket_idx;
+                size    = sizeof(bucket_t) * nbucket;
+                buckets = realloc(freemap->buckets, size);
+                
+                if (!buckets) {
+                    errno = ENOMEM;
+                    return -1;
+                }
+            }
+        }
+        
+        return 0;
+    }
+    
+    errno = EINVAL;
+    return -1;
+}
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/hash.c b/src/murphy-db/mdb/hash.c
new file mode 100644 (file)
index 0000000..cc10477
--- /dev/null
@@ -0,0 +1,542 @@
+#include <stdint.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <stdio.h>
+#include <errno.h>
+
+#define _GNU_SOURCE
+#include <string.h>
+
+#include <murphy-db/assert.h>
+#include <murphy-db/hash.h>
+#include <murphy-db/list.h>
+
+#ifndef HASH_STATISTICS
+#define HASH_STATISTICS
+#endif
+
+typedef struct mdb_hash_entry_s {
+    mdb_dlist_t  clink;         /* hash link, ie. chaining */
+    mdb_dlist_t  elink;         /* entry link, ie. linking all entries */
+    void        *key;
+    void        *data;
+} hash_entry_t;
+
+typedef struct {
+    mdb_dlist_t  head;
+#ifdef HASH_STATISTICS
+    struct {
+        int curr;
+        int max;
+    }            entries;
+#endif
+} hash_chain_t;
+
+
+typedef struct mdb_hash_s {
+    int                  bits;
+    mdb_hash_function_t  hfunc;
+    mdb_hash_compare_t   hcomp;
+    mdb_hash_print_t     hprint;
+    struct {
+        mdb_dlist_t head;
+#ifdef HASH_STATISTICS
+        int         curr;
+        int         max;
+#endif
+    }                    entries;
+    int                  nchain;
+    hash_chain_t         chains[0];
+} mdb_hash_t;
+
+
+typedef struct {
+    int  nchain;
+    int  bits;
+} table_size_t;
+
+static table_size_t  sizes[]          = {
+    {    2,  2}, {    3,  2}, {    5,  3}, {    7,  3}, {   11,  4},
+    {   13,  4}, {   17,  5}, {   19,  5}, {   23,  5}, {   29,  5},
+    {   31,  5}, {   37,  6}, {   41,  6}, {   43,  6}, {   47,  6},
+    {   53,  6}, {   59,  6}, {   61,  6}, {   67,  7}, {   71,  7},
+    {   73,  7}, {   79,  7}, {   83,  7}, {   89,  7}, {   97,  7},
+    {  101,  7}, {  103,  7}, {  107,  7}, {  109,  7}, {  113,  7},
+    {  127,  7}, {  131,  8}, {  137,  8}, {  139,  8}, {  149,  8},
+    {  151,  8}, {  157,  8}, {  163,  8}, {  167,  8}, {  173,  8},
+    {  179,  8}, {  181,  8}, {  191,  8}, {  193,  8}, {  197,  8},
+    {  199,  8}, {  211,  8}, {  223,  8}, {  227,  8}, {  229,  8},
+    {  233,  8}, {  239,  8}, {  241,  8}, {  251,  8}, {  257,  9},
+    {  263,  9}, {  269,  9}, {  271,  9}, {  277,  9}, {  281,  9},
+    {  283,  9}, {  293,  9}, {  307,  9}, {  311,  9}, {  313,  9},
+    {  317,  9}, {  331,  9}, {  337,  9}, {  347,  9}, {  349,  9},
+    {  353,  9}, {  359,  9}, {  367,  9}, {  373,  9}, {  379,  9},
+    {  383,  9}, {  389,  9}, {  397,  9}, {  401,  9}, {  409,  9},
+    {  419,  9}, {  421,  9}, {  431,  9}, {  433,  9}, {  439,  9},
+    {  443,  9}, {  449,  9}, {  457,  9}, {  461,  9}, {  463,  9},
+    {  467,  9}, {  479,  9}, {  487,  9}, {  491,  9}, {  499,  9},
+    {  503,  9}, {  509,  9}, {  521, 10}, {  523, 10}, {  541, 10},
+    {  547, 10}, {  557, 10}, {  563, 10}, {  569, 10}, {  571, 10},
+    {  577, 10}, {  587, 10}, {  593, 10}, {  599, 10}, {  601, 10},
+    {  607, 10}, {  613, 10}, {  617, 10}, {  619, 10}, {  631, 10},
+    {  641, 10}, {  643, 10}, {  647, 10}, {  653, 10}, {  659, 10},
+    {  661, 10}, {  673, 10}, {  677, 10}, {  683, 10}, {  691, 10},
+    {  701, 10}, {  709, 10}, {  719, 10}, {  727, 10}, {  733, 10},
+    {  739, 10}, {  743, 10}, {  751, 10}, {  757, 10}, {  761, 10},
+    {  769, 10}, {  773, 10}, {  787, 10}, {  797, 10}, {  809, 10},
+    {  811, 10}, {  821, 10}, {  823, 10}, {  827, 10}, {  829, 10},
+    {  839, 10}, {  853, 10}, {  857, 10}, {  859, 10}, {  863, 10},
+    {  877, 10}, {  881, 10}, {  883, 10}, {  887, 10}, {  907, 10},
+    {  911, 10}, {  919, 10}, {  929, 10}, {  937, 10}, {  941, 10},
+    {  947, 10}, {  953, 10}, {  967, 10}, {  971, 10}, {  977, 10},
+    {  983, 10}, {  991, 10}, {  997, 10}, {65535, 16}
+}; 
+static uint32_t  charmap[256] = {
+    /*        00  01  02  03  04  05  06  07  08  09  0a  0b  0c  0d  0e  0f */
+    /* 00 */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    /* 10 */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    /* 20 */   0,  0,  0,  0,  0,  0,  0,  0, 52, 53, 54, 55, 56, 37, 40, 50,
+    /* 30 */   1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 41,  0, 42, 43, 44, 45,
+    /* 40 */  46, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
+    /* 50 */  26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 47, 48, 49, 51, 38,
+    /* 60 */   0, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
+    /* 70 */  26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 57, 58, 59, 60,  0,
+    /* 80 */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    /* 90 */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    /* a0 */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    /* b0 */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    /* c0 */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    /* d0 */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    /* e0 */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    /* f0 */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0
+};
+
+static void htable_reset(mdb_hash_t *, int);
+static table_size_t *get_table_size(int);
+static int print_chain(mdb_hash_t *, int, char *, int);
+
+
+mdb_hash_t *mdb_hash_table_create(int                  max_entries,
+                                  mdb_hash_function_t  hfunc,
+                                  mdb_hash_compare_t   hcomp,
+                                  mdb_hash_print_t     hprint)
+{
+    mdb_hash_t   *htbl;
+    table_size_t *ts;
+    size_t        size;
+    int           i;
+
+    MDB_CHECKARG(hfunc && hcomp && hprint &&
+                 max_entries > 1 && max_entries < 65536, NULL);
+
+    if ((ts = get_table_size(max_entries)) == NULL) {
+        errno = EOVERFLOW;
+        return NULL;
+    }
+
+    size = sizeof(mdb_hash_t) + sizeof(hash_chain_t) * ts->nchain;
+    htbl = calloc(1, size);
+
+    if (!htbl) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    htbl->bits   = ts->bits;
+    htbl->nchain = ts->nchain;
+    htbl->hfunc  = hfunc;
+    htbl->hcomp  = hcomp;
+    htbl->hprint = hprint;
+    
+    MDB_DLIST_INIT(htbl->entries.head);
+
+    for (i = 0; i < htbl->nchain; i++)
+        MDB_DLIST_INIT(htbl->chains[i].head);
+
+    return htbl;
+}
+
+int mdb_hash_table_destroy(mdb_hash_t *htbl)
+{
+    MDB_CHECKARG(htbl, -1);
+
+    htable_reset(htbl, 0);
+    free(htbl);
+
+    return 0;
+}
+
+int mdb_hash_table_reset(mdb_hash_t *htbl)
+{
+    MDB_CHECKARG(htbl, -1);
+
+    htable_reset(htbl, 1);
+
+    return 0;
+}
+
+void *mdb_hash_table_iterate(mdb_hash_t *htbl,void **key_ret,void **cursor_ptr)
+{
+    mdb_dlist_t  *head, *link;
+    hash_entry_t *entry;
+
+    MDB_CHECKARG(htbl && cursor_ptr, NULL);
+
+    head = &htbl->entries.head;
+
+    if (!(link = *cursor_ptr))
+        *cursor_ptr = link = head->next;
+
+    if (link == head)
+        return NULL;
+
+    *cursor_ptr = link->next;
+
+    entry = MDB_LIST_RELOCATE(hash_entry_t, elink, link);
+
+    if (key_ret)
+        *key_ret = entry->key;
+    
+    return entry->data;
+}
+
+int mdb_hash_table_print(mdb_hash_t *htbl, char *buf, int len)
+{
+    char *p, *e;
+    int   i;
+
+    MDB_CHECKARG(htbl && buf && len > 0, 0);
+
+    e = (p = buf) + len;
+    *buf = '\0';
+
+    for (i = 0;  i < htbl->nchain;  i++) {
+        if (!MDB_DLIST_EMPTY(htbl->chains[i].head)
+#ifdef HASH_STATISTICS
+            || htbl->chains[i].entries.max > 0
+#endif
+            )
+            p += print_chain(htbl, i, p, e-p);
+    }
+
+    return p - buf;
+}
+
+int mdb_hash_add(mdb_hash_t *htbl, int klen, void *key, void *data)
+{
+    hash_entry_t *entry;
+    hash_chain_t *chain;
+    int           index;
+
+    MDB_CHECKARG(htbl && key && klen >= 0 && data, -1);
+
+    index = htbl->hfunc(htbl->bits, htbl->nchain, klen, key);
+    chain = htbl->chains + index;
+
+    MDB_DLIST_FOR_EACH(hash_entry_t, clink, entry, &chain->head) {
+        if (htbl->hcomp(klen, key, entry->key) == 0) {
+            if (data == entry->data)
+                return 0;
+            else {
+                errno = EEXIST;
+                return -1;
+            }
+        }
+    }
+
+    if (!(entry = calloc(1, sizeof(hash_entry_t)))) {
+        errno = ENOMEM;
+        return -1;
+    }
+    entry->key  = key;
+    entry->data = data;
+
+    MDB_DLIST_APPEND(hash_entry_t, clink, entry, &chain->head);
+    MDB_DLIST_APPEND(hash_entry_t, elink, entry, &htbl->entries.head);
+
+#ifdef HASH_STATISTICS
+    if (++chain->entries.curr > chain->entries.max)
+        chain->entries.max = chain->entries.curr;
+
+    if (++htbl->entries.curr > htbl->entries.max)
+        htbl->entries.max = htbl->entries.curr;
+#endif
+
+    return 0;
+}
+
+void *mdb_hash_delete(mdb_hash_t *htbl, int klen, void *key)
+{
+    hash_entry_t *entry;
+    hash_entry_t *n;
+    hash_chain_t *chain;
+    int           index;
+    void         *data;
+
+    MDB_CHECKARG(htbl && klen >= 0 && key, NULL);
+
+    index = htbl->hfunc(htbl->bits, htbl->nchain, klen, key);
+    chain = htbl->chains + index;
+
+    MDB_DLIST_FOR_EACH_SAFE(hash_entry_t, clink, entry,n, &chain->head) {
+        if (htbl->hcomp(klen, key, entry->key) == 0) {
+            if (!(data = entry->data))
+                break;
+
+            MDB_DLIST_UNLINK(hash_entry_t, clink, entry);
+            MDB_DLIST_UNLINK(hash_entry_t, elink, entry);
+            free(entry);
+
+#ifdef HASH_STATISTICS
+            if (--chain->entries.curr < 0)
+                chain->entries.curr = 0;
+
+            if (--htbl->entries.curr < 0)
+                htbl->entries.curr = 0;
+#endif
+            return data;
+        }
+    }
+    
+    errno = ENOENT;
+    return NULL;
+}
+
+void *mdb_hash_get_data(mdb_hash_t *htbl, int klen, void *key)
+{
+    hash_entry_t *entry;
+    hash_chain_t *chain;
+    int           index;
+
+    MDB_CHECKARG(htbl && klen >= 0 && key, NULL);
+
+    index = htbl->hfunc(htbl->bits, htbl->nchain, klen, key);
+    chain = htbl->chains + index;
+
+    MDB_DLIST_FOR_EACH(hash_entry_t, clink, entry, &chain->head) {
+        if (htbl->hcomp(klen, key, entry->key) == 0)
+            return entry->data;
+    }
+
+    errno = ENOENT;
+    return NULL;
+}
+
+
+int mdb_hash_function_integer(int bits, int nchain, int klen, void *key)
+{
+    return mdb_hash_function_unsignd(bits, nchain, klen, key);
+}
+
+
+int mdb_hash_function_unsignd(int bits, int nchain, int klen, void *key)
+{
+    uint32_t unsignd;
+
+    if (klen != sizeof(unsignd) || !key ||
+        bits < 1 || bits > 16 ||
+        nchain < (1 << (bits-1)) || nchain >= (1 << bits))
+        return 0;
+
+    unsignd = *(uint32_t *)key;
+
+    return (int)(unsignd % nchain);
+}
+
+
+int mdb_hash_function_string(int bits, int nchain, int klen, void *key)
+{
+    typedef union {
+        uint64_t wide;
+        uint8_t  narrow[8];
+    } hash_t;
+
+    (void)klen;
+
+    uint8_t *varchar = (uint8_t *)key;
+    int      hashval = 0;
+    hash_t   h;
+    int      shift;
+
+    if (varchar && bits >= 1 && bits <= 16 &&
+        nchain > (1 << (bits-1)) && nchain < (1 << bits))
+    {
+        uint8_t s;
+        int     i;
+
+        for (h.wide = 0; (s = *varchar); varchar++)
+            h.wide = 33ULL * h.wide + (uint64_t)charmap[s];
+        
+        if (bits <= 8) {
+            hashval = h.narrow[0] ^ h.narrow[1] ^ h.narrow[2] ^ h.narrow[3] ^
+                      h.narrow[4] ^ h.narrow[5] ^ h.narrow[6] ^ h.narrow[7];
+        }
+        else {
+            shift = (nchain + 7) / 8;
+            for (hashval = h.narrow[0], i = 1;   i < 8;  i++)
+                hashval ^= h.narrow[i] << (i * shift);
+        }
+
+        hashval %= nchain;
+    }
+       
+    return hashval;
+}
+
+int mdb_hash_function_pointer(int bits, int nchain, int klen, void *key)
+{
+#define MASK(t)  ((((uint##t##_t)1) << (sizeof(int) * 8 - 3)) - 1)
+    int hash;
+
+#if __SIZEOF_POINTER__ == 8
+    hash = (int)(((uint64_t)key >> 2) & MASK(64)) % nchain;
+#else
+    hash = ((int)key >> 2) & MASK(32) % nchain;
+#endif
+
+    return hash;
+#undef MASK
+}
+
+int mdb_hash_function_varchar(int bits, int nchain, int klen, void *key)
+{
+    return mdb_hash_function_string(bits, nchain, klen, key);
+}
+
+int mdb_hash_function_blob(int bits, int nchain, int klen, void *key)
+{
+    typedef union {
+        uint64_t wide;
+        uint8_t  narrow[8];
+    } hash_t;
+
+    uint8_t *data  = (uint8_t *)key;
+    int      hashval = 0;
+    hash_t   h;
+    int      shift;
+    int      i;
+
+    if (klen > 0 && data && bits >= 1 && bits <= 16 &&
+        nchain > (1 << (bits-1)) && nchain < (1 << bits))
+    {
+        for (i = 0, h.wide = 0;   i < klen;   i++)
+            h.wide = 33ULL * h.wide + (uint64_t)data[i];
+        
+        if (bits <= 8) {
+            hashval = h.narrow[0] ^ h.narrow[1] ^ h.narrow[2] ^ h.narrow[3] ^
+                      h.narrow[4] ^ h.narrow[5] ^ h.narrow[6] ^ h.narrow[7];
+        }
+        else {
+            shift = (nchain + 7) / 8;
+            for (hashval = h.narrow[0], i = 1;   i < 8;  i++)
+                hashval ^= h.narrow[i] << (i * shift);
+        }
+
+        hashval %= nchain;
+    }
+       
+    return hashval;
+}
+
+
+
+static void htable_reset(mdb_hash_t *htbl, int do_chain_statistics)
+{
+    hash_entry_t *entry;
+    hash_entry_t *n;
+#ifdef HASH_STATISTICS
+    int i;
+#else
+    (void)do_statistics;
+#endif
+
+    MDB_DLIST_FOR_EACH_SAFE(hash_entry_t, elink, entry,n, &htbl->entries.head){
+        MDB_DLIST_UNLINK(hash_entry_t, clink, entry);
+        MDB_DLIST_UNLINK(hash_entry_t, elink, entry);
+        free(entry);
+    }
+
+#ifdef HASH_STATISTICS
+    if (do_chain_statistics) {
+        for (i = 0;   i < htbl->nchain;   i++) {
+            htbl->chains[i].entries.curr = 0;
+        }
+    }
+
+    htbl->entries.curr = 0;
+#endif
+}
+
+static table_size_t *get_table_size(int max_entries)
+{
+    int dim = sizeof(sizes)/sizeof(sizes[0]);
+    int min = 0;
+    int max = dim - 1;
+    int idx;
+#ifdef DEBUG
+    int iterations = 0;
+#endif
+
+    for (;;) {
+#ifdef DEBUG
+        iterations++;
+#endif
+        idx = (min + max) / 2;
+
+        if (max_entries == sizes[idx].nchain)
+            break;
+
+        if (idx == min) {
+            idx = max;
+            break;
+        }
+
+        if (max_entries < sizes[idx].nchain)
+            max = idx;
+        else
+            min = idx;
+    }
+
+#ifdef DEBUG
+    printf("%s(%d) => {%d,%d} @ %d\n", __FUNCTION__, max_entries,
+           sizes[idx].nchain, sizes[idx].bits, iterations);
+#endif
+
+    return sizes + idx;
+}
+
+static int print_chain(mdb_hash_t *htbl, int index, char *buf, int len)
+{
+    hash_chain_t *chain = htbl->chains + index;
+    hash_entry_t *entry;
+    char *p, *e;
+    char key[256];
+
+    e = (p = buf) + len;
+
+#ifdef HASH_STATISTICS
+    p += snprintf(p, e-p, "   %05d: %d/%d\n",
+                  index, chain->entries.curr, chain->entries.max);
+#else
+    p += snprintf(p, e-p, "   %05d\n", index);
+#endif
+
+    MDB_DLIST_FOR_EACH(hash_entry_t, clink, entry, &chain->head) {
+        if (p >= e)
+            break;
+
+        htbl->hprint(entry->key, key, sizeof(key));
+
+        p += snprintf(p, e-p, "      '%s' / %p\n", key, entry->data);
+    }
+
+    return p - buf;
+}
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/index.c b/src/murphy-db/mdb/index.c
new file mode 100644 (file)
index 0000000..51bd1c9
--- /dev/null
@@ -0,0 +1,313 @@
+#include <stdint.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdio.h>
+#include <errno.h>
+
+#define _GNU_SOURCE
+#include <string.h>
+
+#include <murphy-db/assert.h>
+#include "index.h"
+#include "row.h"
+#include "column.h"
+#include "table.h"
+
+#include "transaction.h"
+
+#define INDEX_HASH_CREATE(t)        MDB_HASH_TABLE_CREATE(t,100)
+#define INDEX_SEQUENCE_CREATE(t)    MDB_SEQUENCE_TABLE_CREATE(t,16)
+
+#define INDEX_HASH_DROP(ix)         mdb_hash_table_destroy(ix->hash)
+#define INDEX_SEQUENCE_DROP(ix)     mdb_sequence_table_destroy(ix->sequence)
+
+#define INDEX_HASH_RESET(ix)        mdb_hash_table_reset(ix->hash)
+#define INDEX_SEQUENCE_RESET(ix)    mdb_sequence_table_reset(ix->sequence)
+
+
+
+int mdb_index_create(mdb_table_t *tbl, char **index_columns)
+{
+    mdb_index_t     *ix;
+    mdb_column_t    *col;
+    mqi_data_type_t  type;
+    int              beg, end;
+    int             *idxcols;
+    int              i,j, idx;
+
+    MDB_CHECKARG(tbl && index_columns && index_columns[0], -1);
+
+    ix = &tbl->index;
+
+    beg = end = 0;
+    type = mqi_unknown;
+    idxcols = NULL;
+
+    for (i = 0;    index_columns[i];    i++) {
+        if (!(idx = mdb_hash_get_data(tbl->chash,0,index_columns[i]) - NULL)) {
+            errno = ENOENT;
+            return -1;
+        }
+                
+        col = tbl->columns + --idx;
+        col->flags |= MQI_COLUMN_KEY;
+
+        if (i == 0) {
+            type = col->type;
+            beg  = col->offset;
+            end  = beg + col->length;
+        }
+        else {
+            type = mqi_blob;
+
+            if (col->offset == end)
+                end += col->length;
+            else if (col->offset == beg - col->length)
+                beg = col->offset;
+            else {
+                type = mqi_unknown;
+                break; /* not an adjacent column */
+            }
+        }
+            
+        if (!(idxcols = realloc(idxcols, sizeof(int *) * (i+1)))) {
+            errno = ENOMEM;
+            return -1;
+        }
+        
+        for (j = 0;  j < i;  j++) {
+            if (idx == idxcols[j])
+                break;
+
+            if (idx < idxcols[j]) {
+                memmove(idxcols + j+1, idxcols + j, sizeof(*idxcols) * (i-j));
+                break;
+            }
+        }
+
+        idxcols[j] = idx;
+    }
+
+    if (type == mqi_unknown || beg < 0 || end <= beg ||
+        end - beg > MDB_INDEX_LENGTH_MAX)
+    {
+        free(idxcols);
+        errno = EIO;
+        return -1;
+    }
+
+    ix->type    = type;
+    ix->length  = end - beg;
+    ix->offset  = beg;
+    ix->ncolumn = i;
+    ix->columns = idxcols;
+
+    switch (type) {
+    case mqi_varchar:
+        ix->hash = INDEX_HASH_CREATE(varchar);
+        ix->sequence = INDEX_SEQUENCE_CREATE(varchar);
+        break;
+    case mqi_integer:
+        ix->hash = INDEX_HASH_CREATE(integer);
+        ix->sequence = INDEX_SEQUENCE_CREATE(integer);
+        break;
+    case mqi_unsignd:
+        ix->hash = INDEX_HASH_CREATE(unsignd);
+        ix->sequence = INDEX_SEQUENCE_CREATE(unsignd);
+        break;
+    case mqi_blob:
+        ix->hash = INDEX_HASH_CREATE(blob);
+        ix->sequence = INDEX_SEQUENCE_CREATE(blob);
+        break;
+    default:
+        free(idxcols);
+        memset(ix, 0, sizeof(*ix));
+        break;
+    }
+
+    return 0;
+}
+
+void mdb_index_drop(mdb_table_t *tbl)
+{
+    mdb_index_t *ix;
+
+    MDB_CHECKARG(tbl,);
+
+    ix = &tbl->index;
+
+    if (MDB_INDEX_DEFINED(ix)) {
+        INDEX_HASH_DROP(ix);
+        INDEX_SEQUENCE_DROP(ix);
+        
+        free(ix->columns);
+        
+        memset(ix, 0, sizeof(*ix));
+        
+        ix->type = mqi_unknown;
+    }
+}
+
+void mdb_index_reset(mdb_table_t *tbl)
+{
+    mdb_index_t *ix;
+
+    MDB_CHECKARG(tbl,);
+
+    ix = &tbl->index;
+
+    if (MDB_INDEX_DEFINED(ix)) {
+        INDEX_HASH_RESET(ix);
+        INDEX_SEQUENCE_RESET(ix);
+    }
+}
+
+
+int mdb_index_insert(mdb_table_t   *tbl,
+                     mdb_row_t     *row,
+                     int            ignore)
+{
+    mdb_index_t    *ix;
+    int             lgh;
+    void           *key;
+    mdb_hash_t     *hash;
+    mdb_sequence_t *seq;
+    mdb_row_t      *old;
+    uint32_t        txdepth;
+
+    MDB_CHECKARG(tbl && row, -1);
+
+    ix = &tbl->index;
+
+    if (!MDB_INDEX_DEFINED(ix))
+        return 1;               /* fake a sucessful insertion */
+
+    hash = ix->hash;
+    seq  = ix->sequence;
+    lgh  = ix->length;
+    key  = (void *)row->data + ix->offset;
+
+    if (mdb_hash_add(hash, lgh,key, row) == 0) {
+        mdb_sequence_add(seq, lgh,key, row);
+        return 1;
+    }
+
+    /*
+     * we have a duplicate at hand
+     */
+
+    if (ignore) { /* replace the duplicate with the new row */
+
+        /* TODO: move the transaction & log related stuff to table,
+           ie. here deal with indexes only */
+        if ((txdepth = mdb_transaction_get_depth()) < 1) {
+            errno = EIO;
+            return -1;
+        }
+
+        if (!(old = mdb_hash_delete(hash, lgh,key)) ||
+            (old != mdb_sequence_delete(seq, lgh,key)))
+        {
+            /* something is really broken: get out quickly */
+            errno = EIO;
+            return -1;
+        }
+        else {
+            if (mdb_row_delete(tbl, old, 0,0) < 0 ||
+                mdb_log_change(tbl, txdepth, mdb_log_update, old, row) < 0)
+            {
+                return -1;
+            }
+
+            mdb_hash_add(hash, lgh,key, row);
+            mdb_sequence_add(seq, lgh,key, row);
+        }
+    }
+    else { /* duplicate insertion is an error. keep the original row */
+        mdb_row_delete(tbl, row, 0, 1);
+        return -1;
+    }
+
+    return 0;
+}
+
+int mdb_index_delete(mdb_table_t *tbl, mdb_row_t *row)
+{
+    mdb_index_t    *ix;
+    int             lgh;
+    void           *key;
+    mdb_hash_t     *hash;
+    mdb_sequence_t *seq;
+
+    MDB_CHECKARG(tbl && row, -1);
+
+    ix = &tbl->index;
+
+    if (!MDB_INDEX_DEFINED(ix))
+        return 0;
+
+    hash = ix->hash;
+    seq  = ix->sequence;
+    lgh  = ix->length;
+    key  = (void *)row->data + ix->offset;
+    
+    if (mdb_hash_delete(hash, lgh,key)    != row ||
+        mdb_sequence_delete(seq, lgh,key) != row)
+    {
+        errno = EIO;
+        return -1;
+    }
+
+    return 0;
+}
+
+mdb_row_t *mdb_index_get_row(mdb_table_t *tbl, int idxlen, void *idxval)
+{
+    mdb_index_t *ix;
+
+    MDB_CHECKARG(tbl && idxlen >= 0 && idxval, NULL);
+
+    ix = &tbl->index;
+
+    return mdb_hash_get_data(ix->hash, idxlen, idxval);
+}
+
+int mdb_index_print(mdb_table_t *tbl, char *buf, int len)
+{
+#define PRINT(args...)  if (e > p) p += snprintf(p, e-p, args)
+    mdb_index_t *ix; 
+    const char  *sep;
+    char        *p, *e;
+    int          i;
+
+    MDB_CHECKARG(tbl && buf && len > 0, 0);
+
+    ix = &tbl->index;
+
+    MDB_PREREQUISITE(MDB_INDEX_DEFINED(ix), 0);
+
+    e = (p = buf) + len;
+
+    PRINT("index columns: ");
+
+    for (i = 0, sep = "";   i < ix->ncolumn;   i++, sep = ",")
+        PRINT("%s%02d", sep, ix->columns[i]);
+  
+    PRINT("\n    type    offset length\n    ---------------------"
+          "\n    %-7s   %4d   %4d\n",
+          mqi_data_type_str(ix->type), ix->offset, ix->length);
+
+    return p - buf;
+
+#undef PRINT
+}
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/index.h b/src/murphy-db/mdb/index.h
new file mode 100644 (file)
index 0000000..671a0d4
--- /dev/null
@@ -0,0 +1,44 @@
+#ifndef __MDB_INDEX_H__
+#define __MDB_INDEX_H__
+
+
+typedef struct mdb_table_s mdb_table_t;
+typedef struct mdb_row_s   mdb_row_t;
+
+#include <murphy-db/mqi-types.h>
+#include <murphy-db/hash.h>
+#include <murphy-db/sequence.h>
+
+#define MDB_INDEX_LENGTH_MAX 8192
+
+#define MDB_INDEX_DEFINED(ix) ((ix)->type != mqi_unknown)
+
+typedef struct {
+    mqi_data_type_t  type;
+    int              length;
+    int              offset;
+    mdb_hash_t      *hash;
+    mdb_sequence_t  *sequence;
+    int              ncolumn;
+    int             *columns;   /* sorted */
+} mdb_index_t;
+
+
+int mdb_index_create(mdb_table_t *, char **);
+void mdb_index_drop(mdb_table_t *);
+void mdb_index_reset(mdb_table_t *);
+int mdb_index_insert(mdb_table_t *, mdb_row_t *, int);
+int mdb_index_delete(mdb_table_t *, mdb_row_t *);
+mdb_row_t *mdb_index_get_row(mdb_table_t *, int, void *);
+int mdb_index_print(mdb_table_t *, char *, int);
+
+
+#endif /* __MDB_INDEX_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/log.c b/src/murphy-db/mdb/log.c
new file mode 100644 (file)
index 0000000..9669841
--- /dev/null
@@ -0,0 +1,401 @@
+#include <stdint.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <stdio.h>
+#include <errno.h>
+
+#define _GNU_SOURCE
+#include <string.h>
+
+#include <murphy-db/assert.h>
+#include <murphy-db/hash.h>
+#include <murphy-db/sequence.h>
+#include "log.h"
+#include "row.h"
+#include "table.h"
+
+#ifndef LOG_STATISTICS
+#define LOG_STATISTICS
+#endif
+
+#define LOG_COMMON_FIELDS   \
+    mdb_dlist_t     vlink;  \
+    mdb_dlist_t     hlink;  \
+    uint32_t        depth
+
+typedef struct {
+    LOG_COMMON_FIELDS;
+} log_t;
+
+typedef struct {
+    LOG_COMMON_FIELDS;
+} tx_log_t;
+
+typedef struct {
+    LOG_COMMON_FIELDS;
+    mdb_table_t *table;
+    mdb_dlist_t  changes;
+} tbl_log_t;
+
+typedef struct {
+    mdb_dlist_t     link;
+    mdb_log_type_t  type;
+    mdb_row_t      *before;
+    mdb_row_t      *after;
+} change_t;
+
+
+
+static inline log_t *new_log(mdb_dlist_t *, mdb_dlist_t *, uint32_t, int);
+static inline void delete_log(log_t *);
+static inline log_t *get_last_vlog(mdb_dlist_t *);
+static tx_log_t *get_tx_log(uint32_t);
+static tbl_log_t *get_tbl_log(mdb_dlist_t *, mdb_dlist_t *, uint32_t,
+                              mdb_table_t *);
+
+static MDB_DLIST_HEAD(tx_head);
+
+int mdb_log_create(mdb_table_t *tbl)
+{
+    MDB_CHECKARG(tbl, -1);
+
+    MDB_DLIST_INIT(tbl->logs);
+
+    return 0;
+}
+
+
+int mdb_log_change(mdb_table_t    *tbl,
+                   uint32_t        depth,
+                   mdb_log_type_t  type,
+                   mdb_row_t      *before,
+                   mdb_row_t      *after)
+{
+    tx_log_t  *txlog;
+    tbl_log_t *tblog;
+    change_t  *change;
+
+    MDB_CHECKARG(tbl, -1);
+
+    if (!depth)
+        return 0;
+
+    if (!(txlog = get_tx_log(depth)) ||
+        !(tblog = get_tbl_log(&tbl->logs, &txlog->hlink, depth, tbl)))
+    {
+        return -1;
+    }
+
+    if (!(change = calloc(1, sizeof(change_t)))) {
+        errno = ENOMEM;
+        return -1;
+    }
+
+    change->type   = type;
+    change->before = before;
+    change->after  = after;
+
+    MDB_DLIST_PREPEND(change_t, link, change, &tblog->changes);
+
+    return 0;
+}
+
+mdb_log_entry_t *mdb_log_transaction_iterate(uint32_t   depth,
+                                             void     **cursor_ptr,
+                                             int        delete)
+{
+    typedef struct {
+        mdb_dlist_t     *hhead;
+        mdb_dlist_t     *chead;
+        mdb_dlist_t     *hlink;
+        mdb_dlist_t     *clink;
+        mdb_log_entry_t  entry;
+    } cursor_t;
+
+    static cursor_t  empty_cursor;
+
+    cursor_t        *cursor;
+    tx_log_t        *txlog;
+    tbl_log_t       *tblog;
+    mdb_dlist_t     *hhead;
+    mdb_dlist_t     *chead;
+    change_t        *change;
+    mdb_log_entry_t *entry;
+
+    MDB_CHECKARG(cursor_ptr, NULL);
+
+    if (!depth)
+        return NULL;
+
+    if ((cursor = *cursor_ptr))
+        entry = &cursor->entry;
+    else {
+        if (!(txlog = (tx_log_t *)get_last_vlog(&tx_head)) ||
+            depth > txlog->depth)
+        {
+            return NULL;
+        }
+
+        hhead = &txlog->hlink;
+
+        if (MDB_DLIST_EMPTY(*hhead))
+            return NULL;
+
+        tblog = MDB_LIST_RELOCATE(tbl_log_t, hlink, hhead->next);
+
+        if (MDB_DLIST_EMPTY(tblog->changes))
+            return NULL;
+
+        chead = &tblog->changes;
+
+        if (!(*cursor_ptr = cursor = calloc(1, sizeof(cursor_t))))
+            return NULL;
+        else {
+            entry = &cursor->entry;
+
+            cursor->hhead = hhead;
+            cursor->chead = chead;
+            cursor->hlink = tblog->hlink.next;
+            cursor->clink = chead->next;
+
+            entry->table = tblog->table;
+        }
+    }
+
+    for (;;) {
+        if (cursor->clink == cursor->chead) {
+            if (delete) {
+                tblog = MDB_LIST_RELOCATE(tbl_log_t, changes, cursor->chead);
+                delete_log((log_t *)tblog);
+            }
+        }
+        else {
+            change = MDB_LIST_RELOCATE(change_t, link, cursor->clink);
+            
+            cursor->clink = change->link.next;
+            
+            entry->change = change->type;
+            entry->before = change->before;
+            entry->after  = change->after;
+
+            if (delete) {
+                MDB_DLIST_UNLINK(change_t, link, change);
+                free(change);
+            }
+            
+            return entry;
+        }
+
+        if (cursor->hlink == cursor->hhead) {
+            if (cursor != &empty_cursor) {
+                *cursor_ptr = &empty_cursor;
+                free(cursor);
+            }
+            return NULL;
+        }
+        else {
+            tblog = MDB_LIST_RELOCATE(tbl_log_t, hlink, cursor->hlink);
+            chead = &tblog->changes;
+            
+            cursor->hlink = tblog->hlink.next;
+            cursor->chead = chead;
+            cursor->clink = chead->next;
+            
+            entry->table  = tblog->table;
+        }
+    }
+}
+
+
+
+mdb_log_entry_t *mdb_log_table_iterate(mdb_table_t  *tbl,
+                                       void        **cursor_ptr,
+                                       int           delete)
+{
+    typedef struct {
+        mdb_dlist_t     *vhead;
+        mdb_dlist_t     *chead;
+        mdb_dlist_t     *vlink;
+        mdb_dlist_t     *clink;
+        mdb_log_entry_t  entry;
+    } cursor_t;
+
+    static cursor_t  empty_cursor;
+
+    cursor_t        *cursor;
+    tbl_log_t       *tblog;
+    mdb_dlist_t     *vhead;
+    mdb_dlist_t     *chead;
+    change_t        *change;
+    mdb_log_entry_t *entry;
+
+    MDB_CHECKARG(tbl && cursor_ptr, NULL);
+
+    if ((cursor = *cursor_ptr))
+        entry = &cursor->entry;
+    else {
+        vhead = &tbl->logs;
+
+        if (MDB_DLIST_EMPTY(*vhead))
+            return NULL;
+
+        if (!(tblog = (tbl_log_t *)get_last_vlog(vhead)))
+            return NULL;
+
+        if (tblog->table != tbl || MDB_DLIST_EMPTY(tblog->changes))
+            return NULL;
+
+        chead = &tblog->changes;
+
+        if (!(*cursor_ptr = cursor = calloc(1, sizeof(cursor_t))))
+            return NULL;
+        else {
+            entry = &cursor->entry;
+
+            cursor->vhead = vhead;
+            cursor->chead = chead;
+            cursor->vlink = tblog->vlink.prev;
+            cursor->clink = chead->next;
+
+            entry->table = tblog->table;
+        }
+    }
+
+    for (;;) {
+        if (cursor->clink == cursor->chead) {
+            if (delete) {
+                tblog = MDB_LIST_RELOCATE(tbl_log_t, changes, cursor->chead);
+                delete_log((log_t *)tblog);
+            }
+        }
+        else {
+            change = MDB_LIST_RELOCATE(change_t, link, cursor->clink);
+            
+            cursor->clink = change->link.next;
+            
+            entry->change = change->type;
+            entry->before = change->before;
+            entry->after  = change->after;
+
+            if (delete) {
+                MDB_DLIST_UNLINK(change_t, link, change);
+                free(change);
+            }
+            
+            return entry;
+        }
+
+        if (cursor->vlink == cursor->vhead) {
+            if (cursor != &empty_cursor) {
+                *cursor_ptr = &empty_cursor;
+                free(cursor);
+            }
+            return NULL;
+        }
+        else {
+            tblog = MDB_LIST_RELOCATE(tbl_log_t, vlink, cursor->vlink);
+            chead = &tblog->changes;
+            
+            cursor->vlink = tblog->vlink.prev;
+            cursor->chead = chead;
+            cursor->clink = chead->next;
+            
+            if (tbl != tblog->table)
+                return NULL;
+        }
+    }
+}
+
+
+
+static inline log_t *new_log(mdb_dlist_t *vhead,
+                             mdb_dlist_t *hhead,
+                             uint32_t     depth,
+                             int          size)
+{
+    log_t *log;
+
+    if ((log = calloc(1, size))) {
+        MDB_DLIST_APPEND(mdb_log_t, vlink, log, vhead);
+
+        if (hhead)
+            MDB_DLIST_APPEND(mdb_log_t, hlink, log, hhead);
+        else
+            MDB_DLIST_INIT(log->hlink);
+
+        log->depth = depth;
+    }
+    
+    return log;
+}
+
+static inline void delete_log(log_t *log)
+{
+    MDB_DLIST_UNLINK(log_t, vlink, log);
+    MDB_DLIST_UNLINK(log_t, hlink, log);
+    
+    free(log);
+}
+
+
+static inline log_t *get_last_vlog(mdb_dlist_t *vhead)
+{
+    if (MDB_DLIST_EMPTY(*vhead))
+        return NULL;
+
+    return MDB_LIST_RELOCATE(log_t, vlink, vhead->prev);
+}
+
+
+static tx_log_t *get_tx_log(uint32_t depth)
+{
+    tx_log_t *log;
+
+    if (!(log = (tx_log_t *)get_last_vlog(&tx_head)) || depth > log->depth) {
+        return (tx_log_t *)new_log(&tx_head, NULL, depth, sizeof(*log));
+    }
+
+    if (depth < log->depth) {
+        errno = ENOKEY;
+        return NULL;
+    }
+        
+    return log;
+}
+
+static tbl_log_t *get_tbl_log(mdb_dlist_t *vhead,
+                              mdb_dlist_t *hhead,
+                              uint32_t     depth,
+                              mdb_table_t *tbl)
+{
+    tbl_log_t *log;
+
+    if (!(log = (tbl_log_t *)get_last_vlog(vhead)) || depth > log->depth) {
+        if ((log = (tbl_log_t *)new_log(vhead, hhead, depth, sizeof(*log)))) {
+            log->table = tbl;
+            MDB_DLIST_INIT(log->changes);
+        }
+    }
+
+    if (tbl != log->table) {
+        errno = EINVAL;
+        return NULL;
+    }
+
+    if (depth < log->depth) {
+        errno = ENOKEY;
+        return NULL;
+    }
+        
+    return log;
+}
+
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/log.h b/src/murphy-db/mdb/log.h
new file mode 100644 (file)
index 0000000..93bca41
--- /dev/null
@@ -0,0 +1,51 @@
+#ifndef __MDB_LOG_H__
+#define __MDB_LOG_H__
+
+#include <murphy-db/list.h>
+#include "row.h"
+
+typedef struct mdb_table_s  mdb_table_t;
+
+#define MDB_TRANSACTION_LOG_FOR_EACH(depth, entry, curs)                 \
+    for (curs = NULL;  (entry = mdb_log_transaction_iterate(depth, &curs, 0));)
+
+#define MDB_TRANSACTION_LOG_FOR_EACH_DELETE(depth, entry, curs)       \
+    for (curs = NULL;  (entry = mdb_log_transaction_iterate(depth, &curs, 1));)
+
+#define MDB_TABLE_LOG_FOR_EACH(table, entry, curs)                 \
+    for (curs = NULL;  (entry = mdb_log_table_iterate(table, &curs, 0));)
+
+#define MDB_TABLE_LOG_FOR_EACH_DELETE(table, entry, curs)       \
+    for (curs = NULL;  (entry = mdb_log_table_iterate(table, &curs, 1));)
+
+typedef enum {
+    mdb_log_unknown = 0,
+    mdb_log_insert,
+    mdb_log_delete,
+    mdb_log_update,
+} mdb_log_type_t;
+
+typedef struct {
+    mdb_table_t    *table;
+    mdb_log_type_t  change;
+    mdb_row_t      *before;
+    mdb_row_t      *after;
+} mdb_log_entry_t;
+
+
+int mdb_log_create(mdb_table_t *);
+int mdb_log_change(mdb_table_t *, uint32_t, mdb_log_type_t,
+                   mdb_row_t *, mdb_row_t *);
+mdb_log_entry_t *mdb_log_transaction_iterate(uint32_t, void **, int);
+mdb_log_entry_t *mdb_log_table_iterate(mdb_table_t *, void **, int);
+
+
+#endif /* __MDB_LOG_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/mqi-types.c b/src/murphy-db/mdb/mqi-types.c
new file mode 100644 (file)
index 0000000..f34ff3e
--- /dev/null
@@ -0,0 +1,130 @@
+#include <stdio.h>
+#include <string.h>
+
+#include <murphy-db/mqi-types.h>
+
+const char *mqi_data_type_str(mqi_data_type_t type)
+{
+    switch (type) {
+    case mqi_integer:  return "integer";
+    case mqi_unsignd:  return "unsigned";
+    case mqi_varchar:  return "varchar";
+    case mqi_blob:     return "blob";
+    default:           return "unknown";
+    }
+}
+
+int mqi_data_compare_integer(int datalen, void *data1, void *data2)
+{
+    int32_t integer1;
+    int32_t integer2;
+
+    if (datalen != sizeof(int32_t) || !data1 || !data2)
+        return 0;
+
+    integer1 = *(int32_t *)data1;
+    integer2 = *(int32_t *)data2;
+
+
+    return (integer1 - integer2);
+}
+
+int mqi_data_compare_unsignd(int datalen, void *data1, void *data2)
+{
+    uint32_t unsigned1;
+    uint32_t unsigned2;
+
+    if (datalen != sizeof(uint32_t) || !data1 || !data2)
+        return 0;
+
+    unsigned1 = *(uint32_t *)data1;
+    unsigned2 = *(uint32_t *)data2;
+
+    if (unsigned1 < unsigned2)
+        return -1;
+
+    if (unsigned1 > unsigned2)
+        return 1;
+
+    return 0;
+}
+
+int mqi_data_compare_string(int datalen, void *data1, void *data2)
+{
+    const char *varchar1 = (const char *)data1;
+    const char *varchar2 = (const char *)data2;
+
+    (void)datalen;
+
+    if (!varchar1 || !varchar1[0] || !varchar2 || !varchar2[0])
+        return 0;
+
+    return strcmp(varchar1, varchar2);
+}
+
+int mqi_data_compare_pointer(int datalen, void *data1, void *data2)
+{
+    (void)datalen;
+
+    return (data1 - data2);
+}
+
+int mqi_data_compare_varchar(int datalen, void *data1, void *data2)
+{
+    return mqi_data_compare_string(datalen, data1, data2);
+}
+
+int mqi_data_compare_blob(int datalen, void *data1, void *data2)
+{
+    if (!datalen || !data1 || !data2)
+        return 0;
+
+    return memcmp(data1, data2, datalen);
+}
+
+int mqi_data_print_integer(void *data, char *buf, int len)
+{
+    int32_t integer = *(int32_t *)data;
+
+    return snprintf(buf, len, "%d", integer);
+}
+
+int mqi_data_print_unsignd(void *data, char *buf, int len)
+{
+    uint32_t unsignd = *(uint32_t *)data;
+
+    return snprintf(buf, len, "%u", unsignd);
+}
+
+int mqi_data_print_string(void *data, char *buf, int len)
+{
+    const char *varchar = (const char *)data;
+
+    return snprintf(buf, len, "%s", varchar);
+}
+
+int mqi_data_print_pointer(void *data, char *buf, int len)
+{
+    return snprintf(buf, len, "%p", data);
+}
+
+int mqi_data_print_varchar(void *data, char *buf, int len)
+{
+    const char *varchar = (const char *)data;
+
+    return snprintf(buf, len, "%s", varchar);
+}
+
+int mqi_data_print_blob(void *data, char *buf, int len)
+{
+    return 0;
+}
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/row.c b/src/murphy-db/mdb/row.c
new file mode 100644 (file)
index 0000000..d557580
--- /dev/null
@@ -0,0 +1,126 @@
+#include <stdint.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdio.h>
+#include <errno.h>
+
+#define _GNU_SOURCE
+#include <string.h>
+
+#include <murphy-db/assert.h>
+#include "row.h"
+#include "table.h"
+#include "index.h"
+#include "column.h"
+
+
+
+mdb_row_t *mdb_row_create(mdb_table_t *tbl)
+{
+    mdb_row_t *row;
+
+    MDB_CHECKARG(tbl, NULL);
+
+    if (!(row = calloc(1, sizeof(mdb_row_t) + tbl->dlgh))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    MDB_DLIST_APPEND(mdb_row_t, link, row, &tbl->rows);
+
+    return row;
+}
+
+mdb_row_t *mdb_row_duplicate(mdb_table_t *tbl, mdb_row_t *row)
+{
+    mdb_row_t *dup;
+
+    MDB_CHECKARG(tbl && row, NULL);
+
+    if (!(dup = calloc(1, sizeof(mdb_row_t) + tbl->dlgh))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    MDB_DLIST_INIT(dup->link);
+    memcpy(dup->data, row->data, tbl->dlgh);
+
+    return dup;
+}
+
+int mdb_row_delete(mdb_table_t *tbl,
+                   mdb_row_t   *row,
+                   int          index_update,
+                   int          free_it)
+{
+    int sts = 0;
+
+    (void)tbl;
+
+    MDB_CHECKARG(row, -1);
+
+    if (index_update && mdb_index_delete(tbl, row) < 0)
+        sts = -1;
+
+    if (!MDB_DLIST_EMPTY(row->link))
+        MDB_DLIST_UNLINK(mdb_row_t, link, row);
+
+    if (free_it)
+        free(row);
+    else
+        MDB_DLIST_INIT(row->link);
+
+    return sts;
+}
+
+int mdb_row_update(mdb_table_t       *tbl,
+                   mdb_row_t         *row,
+                   mqi_column_desc_t *cds,
+                   void              *data,
+                   int                index_update)
+{
+    mdb_column_t      *columns;
+    mqi_column_desc_t *source_dsc;
+    int                cindex;
+    int                i;
+
+    MDB_CHECKARG(tbl && row && cds && data, -1);
+
+    columns = tbl->columns;
+
+    if (index_update)
+        mdb_index_delete(tbl, row);
+
+    for (i = 0;   (cindex = (source_dsc = cds + i)->cindex) >= 0;    i++)
+        mdb_column_write(columns + cindex, row->data, source_dsc, data); 
+    
+    if (index_update)
+        mdb_index_insert(tbl, row, 0);
+
+    return 0;
+}
+
+int mdb_row_copy_over(mdb_table_t *tbl, mdb_row_t *dst, mdb_row_t *src)
+{
+    MDB_CHECKARG(tbl && dst && src, -1);
+
+    if (mdb_index_delete(tbl, dst) < 0)
+        return -1;
+    
+    memcpy(dst->data, src->data, tbl->dlgh);
+    
+    if (mdb_index_insert(tbl, dst, 0) < 0)
+        return -1;
+    
+    return 0;
+}
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/row.h b/src/murphy-db/mdb/row.h
new file mode 100644 (file)
index 0000000..d0a0c02
--- /dev/null
@@ -0,0 +1,28 @@
+#ifndef __MDB_ROW_H__
+#define __MDB_ROW_H__
+
+#include <murphy-db/mqi-types.h>
+#include <murphy-db/list.h>
+
+typedef struct mdb_table_s  mdb_table_t;
+
+typedef struct mdb_row_s {
+    mdb_dlist_t  link;
+    uint8_t      data[0];
+} mdb_row_t;
+
+mdb_row_t *mdb_row_create(mdb_table_t *);
+mdb_row_t *mdb_row_duplicate(mdb_table_t *, mdb_row_t *);
+int mdb_row_delete(mdb_table_t *, mdb_row_t *, int, int);
+int mdb_row_update(mdb_table_t *, mdb_row_t *, mqi_column_desc_t *,void *,int);
+int mdb_row_copy_over(mdb_table_t *, mdb_row_t *, mdb_row_t *);
+
+#endif /* __MDB_ROW_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/sequence.c b/src/murphy-db/mdb/sequence.c
new file mode 100644 (file)
index 0000000..78b3d36
--- /dev/null
@@ -0,0 +1,300 @@
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+
+#define _GNU_SOURCE
+#include <string.h>
+
+
+#ifndef SEQUENCE_STATISTICS
+#define SEQUENCE_STATISTICS
+#endif
+
+#include <murphy-db/assert.h>
+#include <murphy-db/sequence.h>
+
+typedef struct mdb_sequence_entry_s {
+    void         *key;
+    void         *data;
+} sequence_entry_t;
+
+
+typedef struct mdb_sequence_s {
+    int                     alloc;
+    mdb_sequence_compare_t  scomp;
+    mdb_sequence_print_t    sprint;
+#ifdef SEQUENCE_STATISTICS
+    int                     max_entry;
+#endif
+    int                     size;
+    int                     nentry;
+    sequence_entry_t       *entries;
+} mdb_sequence_t;
+
+
+
+mdb_sequence_t *mdb_sequence_table_create(int                    alloc,
+                                          mdb_sequence_compare_t scomp,
+                                          mdb_sequence_print_t   sprint)
+{
+    mdb_sequence_t *seq;
+
+    MDB_CHECKARG(scomp && sprint && alloc > 0 && alloc < 65536, NULL);
+    
+    if (!(seq = calloc(1, sizeof(mdb_sequence_t)))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    seq->alloc  = alloc;
+    seq->scomp  = scomp;
+    seq->sprint = sprint;
+
+    return seq;
+}
+
+int mdb_sequence_table_destroy(mdb_sequence_t *seq)
+{
+    MDB_CHECKARG(seq, -1);
+
+    free(seq->entries);
+    free(seq);
+
+    return 0;
+}
+
+int mdb_sequence_table_get_size(mdb_sequence_t *seq)
+{
+    MDB_CHECKARG(seq, -1);
+
+    return seq->nentry;
+}
+
+int mdb_sequence_table_reset(mdb_sequence_t *seq)
+{
+    MDB_CHECKARG(seq, -1);
+
+    free(seq->entries);
+
+    seq->size    = 0;
+    seq->nentry  = 0;
+    seq->entries = NULL;
+
+    return 0;
+}
+
+int mdb_sequence_table_print(mdb_sequence_t *seq, char *buf, int len)
+{
+    sequence_entry_t *entry;
+    char             *p, *e;
+    int               i;
+    char              key[256];
+
+    MDB_CHECKARG(seq && buf && len > 0, 0);
+
+    e = (p = buf) + len;
+    *buf = '\0';
+
+    for (i = 0;  i < seq->nentry && p < e;  i++) {
+        entry = seq->entries + i;
+
+        seq->sprint(entry->key, key, sizeof(key));
+
+        p += snprintf(p, e-p, "   %05d: '%s' / %p\n", i, key, entry->data);
+    }
+
+    return p - buf;
+}
+
+int mdb_sequence_add(mdb_sequence_t *seq, int klen, void *key, void *data)
+{
+    sequence_entry_t *entry;
+    int               nentry;
+    size_t            old_length;
+    size_t            length;
+    int               cmp;
+    int               min, max, i;    
+
+    MDB_CHECKARG(seq && key && data, -1);
+
+    nentry = seq->nentry++;
+
+    if ((nentry + 1) > seq->size) {
+        old_length = sizeof(sequence_entry_t) * seq->size;
+        seq->size += seq->alloc;
+        length = sizeof(sequence_entry_t) * seq->size;
+
+        if (!(seq->entries = realloc(seq->entries, length))) {
+            seq->nentry = 0;
+            errno = ENOMEM;
+            return -1;
+        }
+
+        memset(seq->entries + old_length, 0, length - old_length);
+    }
+
+    for (min = 0,  i = (max = nentry)/2;   ;    i = (min+max)/2) {
+        if (!(cmp = seq->scomp(klen, key, seq->entries[i].key)))
+            break;
+
+        if (i == min) {
+            if (cmp > 0)
+                i = max;
+            break;
+        }
+
+        if (cmp < 0)
+            max = i;
+        else 
+            min = i;
+    }
+
+    entry = seq->entries + i;
+
+    if (i < nentry) {
+        memmove(entry+1, entry, sizeof(sequence_entry_t) * (nentry - i));
+    }
+
+    entry->key  = key;
+    entry->data = data;
+
+#ifdef SEQUENCE_STATISTICS
+    if (seq->nentry > seq->max_entry)
+        seq->max_entry = seq->nentry;
+#endif
+
+    return 0;
+}
+
+void *mdb_sequence_delete(mdb_sequence_t *seq, int klen, void *key)
+{
+    sequence_entry_t *entry;
+    int               i;
+    int               min, max;
+    int               cmp;
+    int               found;
+    void             *data;
+    size_t            length;
+
+    MDB_CHECKARG(seq && key, NULL);
+
+    for (found = 0, min = 0, i = (max = seq->nentry)/2;  ;  i = (min+max)/2) {
+        entry = seq->entries + i;
+
+        if (!(cmp = seq->scomp(klen, key, entry->key))) {
+            found = 1;
+            break;
+        }
+
+        if (i == min) {
+            if (i != max) {
+                entry = seq->entries + max;
+                if (!seq->scomp(klen, key, entry->key))
+                    found = 1;
+            }
+            break;
+        }
+         
+        if (cmp < 0)
+            max = i;
+        else
+            min = i;
+    }
+
+    if (!found) {
+        errno = ENOENT;
+        return NULL;
+    }
+
+    data = entry->data;
+
+    if (--seq->nentry <= 0) {
+        free(seq->entries);
+
+        seq->size    = 0;
+        seq->nentry  = 0;
+        seq->entries = NULL;
+    }
+    else {
+        if (i < seq->nentry) {
+            length = sizeof(sequence_entry_t) * (seq->nentry - i);
+            memmove(entry, entry+1, length);
+        }
+
+        if (seq->nentry <= (seq->size - seq->alloc)) {
+            length = sizeof(sequence_entry_t) * (seq->size -= seq->alloc);
+
+            if (!(seq->entries = realloc(seq->entries, length))) {
+                seq->nentry = 0;
+                errno = ENOMEM;
+            }
+        }
+    }
+
+    return data;
+}
+
+
+
+void *mdb_sequence_iterate(mdb_sequence_t *seq, void **cursor_ptr)
+{
+    typedef struct {
+        int   index;
+        int   nentry;
+        void *entries[];
+    } cursor_t;
+
+    static cursor_t   empty_cursor;
+
+    size_t            length;
+    cursor_t         *cursor;
+    int               i;
+
+    MDB_CHECKARG(seq && cursor_ptr, NULL);
+
+    if (!(cursor = *cursor_ptr)) {
+        length = sizeof(cursor_t) + sizeof(void *) * seq->nentry;
+
+        if (!(cursor = malloc(length)))
+            return NULL;
+
+        cursor->index = 0;
+        cursor->nentry = seq->nentry;
+
+        for (i = 0;  i < seq->nentry;  i++)
+            cursor->entries[i] = seq->entries[i].data;
+        
+        *cursor_ptr = cursor;
+    }
+
+    if (cursor->index >= cursor->nentry) {
+        if (*cursor_ptr != &empty_cursor) {
+            *cursor_ptr = &empty_cursor;
+            free(cursor);
+        }
+        return NULL;
+    }
+
+    return (void *)cursor->entries[cursor->index++];
+}
+
+
+void mdb_sequence_cursor_destroy(mdb_sequence_t *seq, void **cursor)
+{
+    (void)seq;
+
+    if (cursor)
+        free(*cursor);
+}
+
+
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/table.c b/src/murphy-db/mdb/table.c
new file mode 100644 (file)
index 0000000..036859f
--- /dev/null
@@ -0,0 +1,758 @@
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+
+#define _GNU_SOURCE
+#include <string.h>
+
+#include <murphy-db/assert.h>
+#include <murphy-db/handle.h>
+#include <murphy-db/sequence.h>
+#include "table.h"
+#include "row.h"
+#include "table.h"
+#include "cond.h"
+#include "transaction.h"
+
+#define TABLE_STATISTICS
+
+
+typedef struct {
+    int          indexed;
+    void        *cursor;
+} table_iterator_t;
+
+
+static mdb_hash_t *table_hash;
+static int         table_count;
+
+static mdb_row_t *table_iterator(mdb_table_t *, table_iterator_t *);
+static int table_print_info(mdb_table_t *, char *, int);
+static int select_conditional(mdb_table_t *, mqi_cond_entry_t *,
+                              mqi_column_desc_t *,void *, int, int);
+static int select_all(mdb_table_t *, mqi_column_desc_t  *, void *, int, int);
+static int select_by_index(mdb_table_t*, int,void *, mqi_column_desc_t*,void*);
+static int update_conditional(mdb_table_t *, mqi_cond_entry_t *,
+                              mqi_column_desc_t *, void *, int);
+static int update_all(mdb_table_t *, mqi_column_desc_t *, void *, int);
+static int update_single_row(mdb_table_t *, mdb_row_t *, mqi_column_desc_t *,
+                             void *, int);
+static int delete_conditional(mdb_table_t *, mqi_cond_entry_t *);
+static int delete_all(mdb_table_t *);
+static int delete_single_row(mdb_table_t *, mdb_row_t *, int);
+
+
+mdb_table_t *mdb_table_create(char *name,
+                              char **index_columns,
+                              mqi_column_def_t *cdefs)
+{
+    mdb_table_t      *tbl;
+    mdb_hash_t       *chash;
+    mqi_data_type_t   type;
+    int               length;
+    int               align;
+    int               ncolumn;
+    mdb_column_t     *columns;
+    mdb_column_t     *col;
+    mqi_column_def_t *cdef;
+    int               dlgh;
+    int               i;
+
+    MDB_CHECKARG(name && cdefs, NULL);
+
+    if (!table_hash && !(table_hash = MDB_HASH_TABLE_CREATE(varchar, 256))) {
+        errno = EIO;
+        return NULL;
+    }
+
+    for (ncolumn = 0;  cdefs[ncolumn].name;  ncolumn++) {
+        cdef   = cdefs + ncolumn;
+        type   = cdef->type;
+        length = cdef->length;
+
+        if (!cdef->name || !cdef->name[0]) {
+            ncolumn = 0;
+            break;
+        }
+
+        if (type == mqi_varchar) {
+            if (length < 1 || length > MDB_COLUMN_LENGTH_MAX) {
+                ncolumn = 0;
+                break;
+            }
+            length++; /* zero termination */
+        }
+        else if (type == mqi_integer) {
+            length = sizeof(int32_t);
+        }
+        else if (type == mqi_unsignd) {
+            length = sizeof(uint32_t);
+        }
+        else {
+            ncolumn = 0;
+            break;
+        }
+    }
+
+    if (!ncolumn) {
+        errno = EINVAL;
+        return NULL;
+    }
+    
+    if (!(tbl = calloc(1, sizeof(mdb_table_t))) ||
+        !(columns = calloc(ncolumn, sizeof(mdb_column_t))))
+    { 
+        free(tbl);
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    if (!(chash = MDB_HASH_TABLE_CREATE(varchar, 16))) {
+        free(tbl);
+        free(columns);
+        return NULL;
+    }
+
+    for (i = 0, dlgh = 0;  i < ncolumn;  i++) {
+        cdef = cdefs  + i;
+        col  = columns + i;
+
+        switch (cdef->type) {
+        case mqi_varchar:  length = cdef->length + 1;  align = 1;    break;
+        case mqi_integer:  length = sizeof(int32_t);   align = 4;    break;
+        case mqi_unsignd:  length = sizeof(uint32_t);  align = 4;    break;
+        default:           length = cdef->length;      align = 2;    break;
+        }
+
+        col->name   = strdup(cdef->name);
+        col->type   = cdef->type;
+        col->length = length;
+        col->offset = (dlgh + (align - 1)) & ~(align - 1);
+
+        dlgh = col->offset + col->length;
+
+        mdb_hash_add(chash, 0,col->name, NULL + (i+1));
+    }
+
+    dlgh = (dlgh + 3) & ~3;
+
+    tbl->name    = strdup(name);
+    tbl->chash   = chash;
+    tbl->ncolumn = ncolumn;
+    tbl->columns = columns;
+    tbl->dlgh   = dlgh;
+
+    MDB_DLIST_INIT(tbl->rows);
+    mdb_log_create(tbl);
+
+    if (mdb_hash_add(table_hash, 0,tbl->name, tbl) < 0) {
+        mdb_table_drop(tbl);
+        return NULL;
+    }
+
+    if (index_columns)
+        mdb_index_create(tbl, index_columns);
+
+    table_count++;
+
+    return tbl;
+}
+
+int mdb_table_drop(mdb_table_t *tbl)
+{
+    mdb_row_t    *row, *n;
+    mdb_column_t *cols;
+    int           i;
+
+    MDB_CHECKARG(tbl, -1);
+
+    mdb_transaction_drop_table(tbl);
+
+    mdb_index_drop(tbl);
+
+    mdb_hash_table_destroy(tbl->chash);
+
+    MDB_DLIST_FOR_EACH_SAFE(mdb_row_t, link, row,n, &tbl->rows)
+        mdb_row_delete(tbl, row, 0, 1);
+
+    for (i = 0, cols = tbl->columns;   i < tbl->ncolumn;    i++)
+        free(cols[i].name);
+
+    free(tbl->columns);
+    free(tbl->name);
+    free(tbl);
+
+    if (table_count > 1)
+        table_count--;
+    else {
+        MDB_HASH_TABLE_DESTROY(table_hash);
+        table_hash  = NULL;
+        table_count = 0;        
+    }
+
+    return 0;
+}
+
+int mdb_table_create_index(mdb_table_t *tbl, char **index_columns)
+{
+    mdb_row_t *row, *n;
+    int        error = 0;
+
+    MDB_CHECKARG(tbl && index_columns && index_columns[0], -1);
+
+    if (MDB_TABLE_HAS_INDEX(tbl)) {
+        errno = EEXIST;
+        return -1;
+    }
+
+    if (mdb_index_create(tbl, index_columns) < 0)
+        return -1;
+
+    MDB_DLIST_FOR_EACH_SAFE(mdb_row_t, link, row,n, &tbl->rows) {
+        if (mdb_index_insert(tbl, row, 0) < 0) {
+            if ((error = errno) != EEXIST)
+                return -1;
+        }
+    }
+
+    if (error) {
+        errno = error;
+        return -1;
+    }
+
+    return 0;
+}
+
+
+int mdb_table_describe(mdb_table_t *tbl, mqi_column_def_t *defs, int len)
+{
+    mdb_column_t *col;
+    mqi_column_def_t *def;
+    int i,n;
+
+    MDB_CHECKARG(tbl && defs && len > 0 && len >= (n = tbl->ncolumn), -1);
+
+    for (i = 0;    i < n;    i++) {
+        col = tbl->columns + i;
+        def = defs + i;
+
+        def->name   = col->name;
+        def->type   = col->type;
+        def->length = col->length;
+        def->flags  = col->flags;
+
+        if (def->type == mqi_varchar && def->length > 0)
+            def->length--;
+    }
+
+    return n;
+}
+
+int mdb_table_insert(mdb_table_t        *tbl,
+                     int                 ignore,
+                     mqi_column_desc_t  *cds,
+                     void              **data)
+{
+    uint32_t   txdepth = mdb_transaction_get_depth();
+    mdb_row_t *row;
+    int        error;
+    int        nrow;
+    int        ninsert;
+    int        i;
+
+    MDB_CHECKARG(tbl && cds && data && data[0], -1);
+
+    for (i = 0, error = 0, ninsert = 0;    data[i];    i++) {
+        if (!(row = mdb_row_create(tbl))) {
+            errno = ENOMEM;
+            return -1;
+        }
+
+        mdb_row_update(tbl, row, cds, data[i], 0);
+
+        if ((nrow = mdb_index_insert(tbl, row, ignore)) < 0) {
+            if ((error = errno) != EEXIST)
+                return -1;
+
+            ninsert = -1;
+        }
+        else if (nrow > 0) {
+            tbl->nrow++;
+
+            if (mdb_log_change(tbl, txdepth, mdb_log_insert, NULL, row) < 0)
+                ninsert = -1;
+            else
+                ninsert += (ninsert >= 0) ? 1 : 0;
+        }        
+
+    } 
+
+    if (error) {
+        errno = error;
+        return -1;
+    }
+
+    return ninsert;
+} 
+
+int mdb_table_select(mdb_table_t       *tbl,
+                     mqi_cond_entry_t  *cond,
+                     mqi_column_desc_t *cds,
+                     void              *results,
+                     int                size,
+                     int                dim)
+{
+    int  ndata;
+
+    MDB_CHECKARG(tbl, -1);
+
+    if (dim > MQI_QUERY_RESULT_MAX)
+        dim = MQI_QUERY_RESULT_MAX;
+
+    if (cond)
+        ndata = select_conditional(tbl, cond, cds, results, size, dim);
+    else
+        ndata = select_all(tbl, cds, results, size, dim);
+
+    return ndata;
+}
+
+int mdb_table_select_by_index(mdb_table_t *tbl,
+                              mqi_variable_t *idxvars,
+                              mqi_column_desc_t *cds,
+                              void *result)
+{
+    mqi_variable_t    *var;
+    mdb_index_t       *ix;
+    mdb_column_t      *col;
+    void              *data;
+    mqi_column_desc_t  src;
+    int                idxlen;
+    char               idxval[MDB_INDEX_LENGTH_MAX];
+    int                i;
+
+    MDB_CHECKARG(tbl && idxvars && cds && result, -1);
+    MDB_PREREQUISITE(MDB_TABLE_HAS_INDEX(tbl), -1);
+
+    ix = &tbl->index;
+    data = idxval - ix->offset;
+    src.offset = 0;
+    idxlen = ix->length;
+
+    for (i = 0;   i < ix->ncolumn;   i++) {
+        var = idxvars + i;
+        col = tbl->columns + (src.cindex = ix->columns[i]);
+
+        if (col->type != var->type) {
+            errno = EINVAL;
+            return -1;
+        }
+
+        mdb_column_write(col, data, &src, var->generic);
+    }
+
+    return select_by_index(tbl, idxlen,idxval, cds, result);
+}
+
+int mdb_table_update(mdb_table_t       *tbl,
+                     mqi_cond_entry_t  *cond,
+                     mqi_column_desc_t *cds,
+                     void              *data)
+{
+    int           index_update = 0;
+    mdb_column_t *col;
+    int           cindex;
+    int           nupdate;
+    int           i;
+
+    MDB_CHECKARG(tbl, -1);
+
+
+    if (MDB_TABLE_HAS_INDEX(tbl)) {
+        for (i = 0;   (cindex = cds[i].cindex) >= 0;    i++) {
+            col = tbl->columns + i;
+            if ((col->flags & MQI_COLUMN_KEY)) {
+                index_update = 1;
+                break;
+            }
+        }
+    }
+
+    if (cond)
+        nupdate = update_conditional(tbl, cond, cds, data, index_update);
+    else
+        nupdate = update_all(tbl, cds, data, index_update);
+
+    return nupdate;
+}
+
+int mdb_table_delete(mdb_table_t *tbl, mqi_cond_entry_t *cond)
+{
+    int ndelete;
+
+    MDB_CHECKARG(tbl, -1);
+
+    if (cond)
+        ndelete = delete_conditional(tbl, cond);
+    else
+        ndelete = delete_all(tbl);
+    
+    return ndelete;
+}
+
+mdb_table_t *mdb_table_find(char *table_name)
+{
+    MDB_CHECKARG(table_name, NULL);
+    MDB_PREREQUISITE(table_hash, NULL);
+
+    return mdb_hash_get_data(table_hash, 0,table_name);
+}
+
+
+int mdb_table_get_column_index(mdb_table_t *tbl, char *column_name)
+{
+    MDB_CHECKARG(tbl && column_name, -1);
+
+    return (mdb_hash_get_data(tbl->chash, 0,column_name) - NULL) - 1;
+}
+
+int mdb_table_get_size(mdb_table_t *tbl)
+{
+    MDB_CHECKARG(tbl, -1);
+
+    return tbl->nrow;
+}
+
+char *mdb_table_get_column_name(mdb_table_t *tbl, int colidx)
+{
+    MDB_CHECKARG(tbl && colidx >= 0 && colidx < tbl->ncolumn, NULL);
+
+    return tbl->columns[colidx].name;
+}
+
+mqi_data_type_t mdb_table_get_column_type(mdb_table_t *tbl, int colidx)
+{
+    MDB_CHECKARG(tbl && colidx >= 0 && colidx < tbl->ncolumn, mqi_error);
+
+    return tbl->columns[colidx].type;
+}
+
+int mdb_table_get_column_size(mdb_table_t *tbl, int colidx)
+{
+    MDB_CHECKARG(tbl && colidx >= 0 && colidx < tbl->ncolumn, -1);
+
+    return tbl->columns[colidx].length;
+}
+
+int mdb_table_print_rows(mdb_table_t *tbl, char *buf, int len)
+{
+    mdb_row_t *row;
+    char      *p, *e;
+    int       l;
+    int       i;
+    char      dashes[1024];
+    table_iterator_t it;
+
+    MDB_CHECKARG(tbl && buf && len > 0, 0);
+
+    e = (p = buf) + len;
+
+    for (i = 0;  i < tbl->ncolumn;  i++)
+        p += mdb_column_print_header(tbl->columns + i, p, e-p);
+
+    if (p + ((l = p - buf) + 3) < e) {
+        if (l > sizeof(dashes) - 1)
+            l = sizeof(dashes) - 1;
+
+        memset(dashes, '-', l);
+        dashes[l] = '\0';
+
+        p += snprintf(p, e-p, "\n%s\n", dashes);
+
+        for (it.cursor = NULL;  (row = table_iterator(tbl, &it)) && p < e;) {
+            for (i = 0;  i < tbl->ncolumn && p < e;  i++)
+                p += mdb_column_print(tbl->columns + i, row->data, p, e-p);
+            if (p < e)
+                p += snprintf(p, e-p, "\n");
+        }
+    }
+
+    return p - buf;
+}
+
+
+
+static mdb_row_t *table_iterator(mdb_table_t *tbl, table_iterator_t *it)
+{
+    mdb_dlist_t *next;
+    mdb_dlist_t *head;
+    mdb_row_t   *row;
+
+    if (!it->cursor)
+        it->indexed = MDB_TABLE_HAS_INDEX(tbl);
+
+    if (it->indexed)
+        row = mdb_sequence_iterate(tbl->index.sequence, &it->cursor);
+    else {
+        head = &tbl->rows;
+        next = it->cursor ? (mdb_dlist_t *)it->cursor : head->next;
+
+        if (next == head)
+            row = NULL;
+        else {
+            row = MDB_LIST_RELOCATE(mdb_row_t, link, next);
+            it->cursor = next->next;
+        }
+    }
+
+    return row;
+}
+
+static int table_print_info(mdb_table_t *tbl, char *buf, int len)
+{
+#define PRINT(args...)  if (e > p) p += snprintf(p, e-p, args)
+
+    mdb_column_t *col;
+    char         *p, *e;
+    int           i;
+
+    MDB_CHECKARG(tbl && buf && len > 0, 0);
+
+    e = (p = buf) + len;
+    *buf = '\0';
+
+    PRINT("table name  : '%s'\n", tbl->name);
+    PRINT("row length  : %d\n"  , tbl->dlgh);
+    PRINT("no of column: %d\n"  , tbl->ncolumn);
+    PRINT("    index name             type     offset length\n"
+          "    ---------------------------------------------\n");
+
+    for (i = 0;  i < tbl->ncolumn;  i++) {
+        col = tbl->columns + i;
+
+        PRINT("    %s %02d: %-16s %-8s   %4d   %4d\n",
+              col->flags & MQI_COLUMN_KEY ? "*" : " ",
+              i+1, col->name, mqi_data_type_str(col->type),
+              col->offset, col->length);
+    }
+
+    p += mdb_index_print(tbl, p, e-p);
+
+    return p - buf;
+
+#undef PRINT
+}
+
+
+
+static int select_conditional(mdb_table_t       *tbl,
+                              mqi_cond_entry_t  *cond,
+                              mqi_column_desc_t *cds,
+                              void              *results,
+                              int                size,
+                              int                dim)
+{
+    mdb_column_t      *columns = tbl->columns;
+    mdb_row_t         *row;
+    mqi_cond_entry_t  *ce;
+    table_iterator_t   it;
+    int                nresult;
+    void              *result;
+    mqi_column_desc_t *result_dsc;
+    int                cindex;
+    int                i;
+
+    for (it.cursor = NULL, nresult = 0;  (row = table_iterator(tbl, &it)); ) {
+        ce = cond;
+        if (mdb_cond_evaluate(tbl, &ce, row->data)) {
+            if (nresult >= dim) {
+                errno = EOVERFLOW;
+                return -1;
+            }
+
+            result = results + (size * nresult++);
+
+            for (i = 0;  (cindex = (result_dsc = cds + i)->cindex) >= 0;   i++)
+                mdb_column_read(result_dsc, result, columns+cindex, row->data);
+        }
+    }
+
+    return nresult;
+}
+
+static int select_all(mdb_table_t       *tbl,
+                      mqi_column_desc_t *cds,
+                      void              *results,
+                      int                size,
+                      int                dim)
+{
+    mdb_column_t      *columns = tbl->columns;
+    mdb_row_t         *row;
+    table_iterator_t   it;
+    int                nresult;
+    void              *result;
+    mqi_column_desc_t *result_dsc;
+    int                cindex;
+    int                j;
+
+    for (it.cursor = NULL, nresult = 0; 
+         (row = table_iterator(tbl, &it));
+         nresult++)
+    {
+        result = results + (size * nresult);
+
+        for (j = 0;   (cindex = (result_dsc = cds + j)->cindex) >= 0;    j++)
+            mdb_column_read(result_dsc, result, columns + cindex, row->data);
+    }
+
+    return nresult;
+}
+
+static int select_by_index(mdb_table_t       *tbl,
+                           int                idxlen,
+                           void              *idxval,
+                           mqi_column_desc_t *cds,
+                           void              *result)
+{
+    mdb_column_t      *columns = tbl->columns;
+    mdb_row_t         *row;
+    mqi_column_desc_t *result_dsc;
+    int                cindex;
+    int                j;
+
+    if (!(row = mdb_index_get_row(tbl, idxlen,idxval)))
+        return 0;
+
+    for (j = 0;   (cindex = (result_dsc = cds + j)->cindex) >= 0;    j++)
+            mdb_column_read(result_dsc, result, columns + cindex, row->data);
+
+    return 1;
+}
+
+
+static int update_conditional(mdb_table_t       *tbl,
+                              mqi_cond_entry_t  *cond,
+                              mqi_column_desc_t *cds,
+                              void              *data,
+                              int                index_update)
+{
+    mdb_row_t        *row;
+    mqi_cond_entry_t *ce;
+    table_iterator_t  it;
+    int               nupdate;
+
+    for (it.cursor = NULL, nupdate = 0;  (row = table_iterator(tbl, &it)); ) {
+        ce = cond;
+        if (mdb_cond_evaluate(tbl, &ce, row->data)) {
+            if (update_single_row(tbl, row, cds, data, index_update) < 0)
+                nupdate = -1;
+            else
+                nupdate += (nupdate >= 0) ? 1 : 0;
+        }
+    }
+
+    return nupdate;
+}
+
+static int update_all(mdb_table_t       *tbl,
+                      mqi_column_desc_t *cds,
+                      void              *data,
+                      int                index_update)
+{
+    mdb_row_t        *row;
+    table_iterator_t  it;
+    int               nupdate;
+
+    for (it.cursor = NULL, nupdate = 0; (row = table_iterator(tbl, &it)); )
+    {
+        if (update_single_row(tbl, row, cds, data, index_update) < 0)
+            nupdate = -1;
+        else
+            nupdate += (nupdate >= 0) ? 1 : 0;
+    }
+
+    if (nupdate < 0)
+        errno = EEXIST;
+
+    return nupdate;
+}
+
+static int update_single_row(mdb_table_t       *tbl,
+                             mdb_row_t         *row, 
+                             mqi_column_desc_t *cds,
+                             void              *data,
+                             int                index_update)
+{
+    mdb_row_t *before  = NULL;
+    uint32_t   txdepth = mdb_transaction_get_depth(); 
+
+    if (txdepth > 0 && !(before = mdb_row_duplicate(tbl, row)))
+        return -1;
+
+    if (mdb_row_update(tbl, row, cds, data, index_update) < 0)
+        return -1;
+
+    if (mdb_log_change(tbl, txdepth, mdb_log_update, before, row) < 0)
+        return -1;
+
+    return 0;
+}
+
+static int delete_conditional(mdb_table_t *tbl, mqi_cond_entry_t *cond)
+{
+    table_iterator_t  it;    
+    mdb_row_t        *row;
+    mqi_cond_entry_t *ce;
+    int               ndelete;
+
+    for (it.cursor = NULL, ndelete = 0; (row = table_iterator(tbl, &it)); )
+    {
+        ce = cond;
+        if (mdb_cond_evaluate(tbl, &ce, row->data)) {
+            if (delete_single_row(tbl, row, 1) < 0)
+                ndelete = -1;
+            else
+                ndelete += (ndelete >= 0) ? 1 : 0;
+        }
+    }
+    
+    return ndelete;
+}
+
+
+static int delete_all(mdb_table_t *tbl)
+{
+    mdb_row_t *row, *n;
+    int        ndelete;
+
+    mdb_index_reset(tbl);
+
+    MDB_DLIST_FOR_EACH_SAFE(mdb_row_t, link, row,n, &tbl->rows) {
+        if (delete_single_row(tbl, row, 0) < 0)
+            ndelete = -1;
+        else
+            ndelete += (ndelete >= 0) ? 1 : 0;
+    }
+
+    return ndelete;
+}
+
+static int delete_single_row(mdb_table_t *tbl, mdb_row_t *row,int index_update)
+{
+    uint32_t txdepth = mdb_transaction_get_depth();
+
+    mdb_row_delete(tbl, row, index_update, !txdepth);
+    mdb_log_change(tbl, txdepth, mdb_log_delete, row, NULL);
+
+    return 0;
+}
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/table.h b/src/murphy-db/mdb/table.h
new file mode 100644 (file)
index 0000000..c944726
--- /dev/null
@@ -0,0 +1,34 @@
+#ifndef __MDB_TABLE_H__
+#define __MDB_TABLE_H__
+
+#include <murphy-db/mdb.h>
+#include <murphy-db/hash.h>
+#include <murphy-db/list.h>
+#include "index.h"
+#include "column.h"
+#include "log.h"
+
+#define MDB_TABLE_HAS_INDEX(t)  MDB_INDEX_DEFINED(&t->index)
+
+typedef struct mdb_table_s {
+    char         *name;
+    mdb_index_t   index;
+    mdb_hash_t   *chash;         /* hash table for column names */
+    int           ncolumn;
+    mdb_column_t *columns;
+    int           dlgh;          /* length of row data */
+    int           nrow;
+    mdb_dlist_t   rows;
+    mdb_dlist_t   logs;         /* transaction logs */
+} mdb_table_t;
+
+
+#endif /* __MDB_TABLE_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/transaction.c b/src/murphy-db/mdb/transaction.c
new file mode 100644 (file)
index 0000000..f47bb8e
--- /dev/null
@@ -0,0 +1,164 @@
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+
+#define _GNU_SOURCE
+#include <string.h>
+
+#include <murphy-db/assert.h>
+#include "transaction.h"
+#include "log.h"
+#include "index.h"
+#include "table.h"
+
+#define TRANSACTION_STATISTICS
+
+
+static uint32_t txdepth;
+
+static int destroy_row(mdb_table_t *, mdb_row_t *);
+static int remove_row(mdb_table_t *, mdb_row_t *);
+static int add_row(mdb_table_t *, mdb_row_t *);
+static int copy_row(mdb_table_t *, mdb_row_t *, mdb_row_t *);
+
+
+uint32_t mdb_transaction_begin(void)
+{
+    return ++txdepth;
+}
+
+int mdb_transaction_commit(uint32_t depth)
+{
+    mdb_log_entry_t  *en;
+    void             *cursor;
+    int               sts = 0, s;
+
+    MDB_CHECKARG(depth > 0 && depth == txdepth, -1);
+
+    MDB_TRANSACTION_LOG_FOR_EACH_DELETE(depth, en, cursor) {
+
+        switch (en->change) {
+
+        case mdb_log_insert:  s = 0;                                     break;
+        case mdb_log_delete: 
+        case mdb_log_update:  s = destroy_row(en->table, en->before);    break;
+        default:              s = -1;                                    break;
+        }
+
+        if (sts == 0)
+            sts = s;
+    }
+    
+    return sts;
+}
+
+int mdb_transaction_rollback(uint32_t depth)
+{
+    mdb_log_entry_t  *en;
+    mdb_table_t      *tbl;
+    void             *cursor;
+    int               sts = 0, s;
+
+    MDB_CHECKARG(depth > 0 && depth == txdepth, -1);
+
+    MDB_TRANSACTION_LOG_FOR_EACH_DELETE(depth, en, cursor) {
+
+        tbl = en->table;
+
+        switch (en->change) {
+
+        case mdb_log_insert:  s = remove_row(tbl, en->after);            break;
+        case mdb_log_delete:  s = add_row(tbl, en->before);              break;
+        case mdb_log_update:  s = copy_row(tbl, en->after, en->before);  break;
+        default:              s = -1;                                    break;
+        }
+
+        if (sts == 0)
+            sts = s;
+    }
+    
+    return sts;
+}
+
+int mdb_transaction_drop_table(mdb_table_t *tbl)
+{
+    mdb_log_entry_t *en;
+    void            *cursor;
+    int              sts = 0, s;
+
+    MDB_CHECKARG(tbl, -1);
+
+    MDB_TABLE_LOG_FOR_EACH_DELETE(tbl, en, cursor) {
+
+        switch (en->change) {
+
+        case mdb_log_insert:  s = 0;                                     break;
+        case mdb_log_delete: 
+        case mdb_log_update:  s = destroy_row(en->table, en->before);    break;
+        default:              s = -1;                                    break;
+        }
+
+        if (sts == 0)
+            sts = s;
+    }
+
+    return sts;
+}
+
+uint32_t mdb_transaction_get_depth(void)
+{
+    return txdepth;
+}
+
+static int destroy_row(mdb_table_t *tbl, mdb_row_t *row)
+{
+    MDB_CHECKARG(tbl && row && MDB_DLIST_EMPTY(row->link), -1);
+
+    return mdb_row_delete(tbl, row, 0, 1);
+}
+
+static int remove_row(mdb_table_t *tbl, mdb_row_t *row)
+{
+    MDB_CHECKARG(tbl && row, -1);
+
+    if (mdb_index_delete(tbl, row) < 0 ||
+        mdb_row_delete(tbl, row, 0, 1) < 0    )
+    {
+        return -1;
+    }
+
+    return 0;
+}
+
+static int add_row(mdb_table_t *tbl, mdb_row_t *row)
+{
+    MDB_CHECKARG(tbl && row, -1);
+
+    MDB_DLIST_APPEND(mdb_row_t, link, row, &tbl->rows);
+
+    return mdb_index_insert(tbl, row, 0);
+}
+
+static int copy_row(mdb_table_t *tbl, mdb_row_t *dst, mdb_row_t *src)
+{
+
+    MDB_CHECKARG(tbl && dst && src && MDB_DLIST_EMPTY(src->link), -1);
+
+    if (src == dst)
+        return 0;
+
+    if (mdb_row_copy_over(tbl,dst,src) < 0 || mdb_row_delete(tbl,src,0,1) < 0)
+        return -1;
+
+    return 0;
+}
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/transaction.h b/src/murphy-db/mdb/transaction.h
new file mode 100644 (file)
index 0000000..bdcbfeb
--- /dev/null
@@ -0,0 +1,21 @@
+#ifndef __MDB_TRANSACTION_H__
+#define __MDB_TRANSACTION_H__
+
+typedef struct mdb_table_s mdb_table_t;
+
+uint32_t mdb_transaction_begin(void);
+int mdb_transaction_commit(uint32_t);
+int mdb_transaction_rollback(uint32_t);
+int mdb_transaction_drop_table(mdb_table_t *);
+uint32_t mdb_transaction_get_depth(void);
+
+
+#endif /* __MDB_TRANSACTION_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mqi/Makefile.am b/src/murphy-db/mqi/Makefile.am
new file mode 100644 (file)
index 0000000..39db6d2
--- /dev/null
@@ -0,0 +1,7 @@
+pkglib_LTLIBRARIES = libmqi.la
+
+libmqi_la_CFLAGS = -I.. -I../include
+
+libmqi_la_SOURCES = ../include/murphy-db/mqi.h \
+                    mqi.c db.h mdb-backend.h mdb-backend.c
+
diff --git a/src/murphy-db/mqi/db.h b/src/murphy-db/mqi/db.h
new file mode 100644 (file)
index 0000000..2cd4240
--- /dev/null
@@ -0,0 +1,39 @@
+#ifndef __MQI_DB_H__
+#define __MQI_DB_H__
+
+typedef struct {
+    uint32_t (*begin_transaction)(void);
+    int (*commit_transaction)(uint32_t);
+    int (*rollback_transaction)(uint32_t);
+    uint32_t (*get_transaction_id)(void);
+    void *(*create_table)(char *, char **, mqi_column_def_t *);
+    int (*create_index)(void *, char **);
+    int (*drop_table)(void *);
+    int (*describe)(void *, mqi_column_def_t *, int);
+    int (*insert_into)(void *, int, mqi_column_desc_t *, void **);
+    int (*select)(void *, mqi_cond_entry_t *, mqi_column_desc_t *,
+                  void *, int, int);
+    int (*select_by_index)(void *, mqi_variable_t *,
+                           mqi_column_desc_t *, void *);
+    int (*update)(void *, mqi_cond_entry_t *, mqi_column_desc_t *,void*);
+    int (*delete_from)(void *, mqi_cond_entry_t *);
+    void *(*find_table)(char *);
+    int (*get_column_index)(void *, char *);
+    int (*get_table_size)(void *);
+    char *(*get_column_name)(void *, int);
+    mqi_data_type_t (*get_column_type)(void *, int);
+    int (*get_column_size)(void *, int);
+    int (*print_rows)(void *, char *, int);
+} mqi_db_functbl_t;
+
+
+
+#endif /* __MQI_DB_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mqi/mdb-backend.c b/src/murphy-db/mqi/mdb-backend.c
new file mode 100644 (file)
index 0000000..dd8e7c0
--- /dev/null
@@ -0,0 +1,203 @@
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+
+#define _GNU_SOURCE
+#include <string.h>
+
+#include <murphy-db/assert.h>
+#include <murphy-db/handle.h>
+
+#include <mdb/table.h>
+#include <mdb/transaction.h>
+
+#include "mdb-backend.h"
+
+
+static uint32_t begin_transaction(void);
+static int      commit_transaction(uint32_t);
+static int      rollback_transaction(uint32_t);
+static uint32_t get_transaction_id(void);
+static void *   create_table(char *, char **, mqi_column_def_t *);
+static int      create_index(void *, char **);
+static int      drop_table(void *);
+static int      describe(void *, mqi_column_def_t *, int);
+static int      insert_into(void *, int, mqi_column_desc_t *, void **);
+static int      select_general(void *, mqi_cond_entry_t *, mqi_column_desc_t *,
+                               void *, int, int);
+static int      select_by_index(void *, mqi_variable_t *, mqi_column_desc_t *,
+                                 void *);
+static int      update(void *, mqi_cond_entry_t *, mqi_column_desc_t*,void*);
+static int      delete_from(void *, mqi_cond_entry_t *);
+static void *   find_table(char *);
+static int      get_column_index(void *, char *);
+static int      get_table_size(void *);
+static char *   get_column_name(void *, int);
+static mqi_data_type_t get_column_type(void *, int);
+static int      get_column_size(void *, int);
+static int      print_rows(void *, char *, int);
+
+static mqi_db_functbl_t functbl = {
+    begin_transaction,
+    commit_transaction,
+    rollback_transaction,
+    get_transaction_id,
+    create_table,
+    create_index,
+    drop_table,
+    describe,
+    insert_into,
+    select_general,
+    select_by_index,
+    update,
+    delete_from,
+    find_table,
+    get_column_index,
+    get_table_size,
+    get_column_name,
+    get_column_type,
+    get_column_size,
+    print_rows
+};
+
+
+mqi_db_functbl_t *mdb_backend_init(void)
+{
+    return &functbl;
+}
+
+
+static uint32_t begin_transaction(void)
+{
+    uint32_t depth = mdb_transaction_begin();
+
+    if (!depth)
+        return MDB_HANDLE_INVALID;
+
+    return depth;
+}
+
+static int commit_transaction(uint32_t depth)
+{
+    return mdb_transaction_commit(depth);
+}
+
+static int rollback_transaction(uint32_t depth)
+{
+    return mdb_transaction_rollback(depth);
+}
+
+static uint32_t get_transaction_id(void)
+{
+    return mdb_transaction_get_depth();
+}
+
+static void *create_table(char *name,
+                          char **index_columns,
+                          mqi_column_def_t *cdefs)
+{
+    return mdb_table_create(name, index_columns, cdefs);
+}
+
+
+static int create_index(void *t, char **index_columns)
+{
+    return mdb_table_create_index((mdb_table_t *)t, index_columns);
+}
+
+static int drop_table(void *t)
+{
+    return mdb_table_drop((mdb_table_t *)t);
+}
+
+static int describe(void *t, mqi_column_def_t *defs, int len)
+{
+    return mdb_table_describe((mdb_table_t *)t, defs, len);
+}
+
+static int insert_into(void               *t,
+                        int                 ignore,
+                        mqi_column_desc_t  *cds,
+                        void              **data)
+{
+    return mdb_table_insert((mdb_table_t *)t, ignore, cds, data);
+} 
+
+static int select_general(void              *t,
+                          mqi_cond_entry_t  *cond,
+                          mqi_column_desc_t *cds,
+                          void              *results,
+                          int                size,
+                          int                dim)
+{
+    return mdb_table_select((mdb_table_t *)t, cond, cds, results, size, dim);
+}
+
+static int select_by_index(void              *t,
+                            mqi_variable_t    *idxvars,
+                            mqi_column_desc_t *cds,
+                            void              *result)
+{
+    return mdb_table_select_by_index((mdb_table_t *)t, idxvars, cds, result);
+}
+
+
+static int update(void              *t,
+                  mqi_cond_entry_t  *cond,
+                  mqi_column_desc_t *cds,
+                  void              *data)
+{
+    return mdb_table_update((mdb_table_t *)t, cond, cds, data);
+}
+
+static int delete_from(void *t, mqi_cond_entry_t *cond)
+{
+    return mdb_table_delete((mdb_table_t *)t, cond);
+}
+
+
+static void *find_table(char *table_name)
+{
+    return mdb_table_find(table_name);
+}
+
+
+static int get_column_index(void *t, char *column_name)
+{
+    return mdb_table_get_column_index((mdb_table_t *)t, column_name);
+}
+
+static int get_table_size(void *t)
+{
+    return  mdb_table_get_size((mdb_table_t *)t);
+}
+
+static char *get_column_name(void *t, int colidx)
+{
+    return  mdb_table_get_column_name((mdb_table_t *)t, colidx);
+}
+
+static mqi_data_type_t get_column_type(void *t, int colidx)
+{
+    return  mdb_table_get_column_type((mdb_table_t *)t, colidx);
+}
+
+static int get_column_size(void *t, int colidx)
+{
+    return  mdb_table_get_column_size((mdb_table_t *)t, colidx);
+}
+
+static int print_rows(void *t, char *buf, int len)
+{
+    return mdb_table_print_rows((mdb_table_t *)t, buf, len);
+}
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mqi/mdb-backend.h b/src/murphy-db/mqi/mdb-backend.h
new file mode 100644 (file)
index 0000000..7865855
--- /dev/null
@@ -0,0 +1,17 @@
+#ifndef __MQI_MDB_BACKEND_H__
+#define __MQI_MDB_BACKEND_H__
+
+#include "db.h"
+
+mqi_db_functbl_t *mdb_backend_init(void);
+
+
+#endif  /* __MQI_MDB_BACKEND_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mqi/mqi.c b/src/murphy-db/mqi/mqi.c
new file mode 100644 (file)
index 0000000..c821708
--- /dev/null
@@ -0,0 +1,598 @@
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+
+#define _GNU_SOURCE
+#include <string.h>
+
+#include <murphy-db/assert.h>
+#include <murphy-db/handle.h>
+#include <murphy-db/mqi.h>
+#include <mdb/table.h>
+#include <mdb/transaction.h>
+#include "mdb-backend.h"
+
+#define MAX_DB 2
+
+#define TX_DEPTH_BITS  4
+#define TX_USEID_BITS  ((sizeof(mqi_handle_t) * 8) - TX_DEPTH_BITS)
+#define TX_DEPTH_MAX   (((mqi_handle_t)1) << TX_DEPTH_BITS)
+#define TX_USEID_MAX   (((mqi_handle_t)1) << TX_USEID_BITS)
+#define TX_DEPTH_MASK  (TX_DEPTH_MAX - 1)
+#define TX_USEID_MASK  (TX_USEID_MAX - 1)
+
+#define TX_DEPTH(h)    ((h) & TX_DEPTH_MASK)
+#define TX_USEID(h)    ((h) & (TX_USEID_MASK << TX_DEPTH_BITS))
+
+#define TX_HANDLE(useid, depth)                                         \
+    (((useid) & (TX_USEID_MASK << TX_DEPTH_BITS)) | ((depth) & TX_DEPTH_MASK))
+
+#define TX_USEID_INCREMENT(u)                                   \
+    (u) = ((((u) + ((mqi_handle_t)1)) << TX_DEPTH_BITS) &       \
+           (TX_USEID_MASK << TX_DEPTH_BITS))
+
+
+#if MQI_TXDEPTH_MAX > (1 << TX_DEPTH_BITS)
+#error "Too few TX_DEPTH_BITS to represent MQI_TXDEPTH_MAX"
+#endif
+
+#define DB_TYPE(db) ((db)->flags & MQI_TABLE_TYPE_MASK)
+
+#define GET_TABLE(tbl, ftb, h, errval)                                      \
+    do {                                                                    \
+        mqi_table_t *t;                                                     \
+        mqi_db_t *db;                                                       \
+        if (!(t = mdb_handle_get_data(table_handle, h)) || !(db = t->db)) { \
+            errno = ENOENT;                                                 \
+            return errval;                                                  \
+        }                                                                   \
+        if (!(tbl = t->handle) || !(ftb = db->functbl)) {                   \
+            errno = EIO;                                                    \
+            return errval;                                                  \
+        }                                                                   \
+    } while(0)
+
+typedef struct {
+    const char       *engine;
+    uint32_t          flags;
+    mqi_db_functbl_t *functbl;
+} mqi_db_t;
+
+typedef struct {
+    mqi_db_t    *db;
+    void        *handle;
+} mqi_table_t;
+
+typedef struct {
+    uint32_t useid;
+    uint32_t txid[MAX_DB];
+} mqi_transaction_t;
+
+
+static int db_register(const char *, uint32_t, mqi_db_functbl_t *);
+
+
+static int        ndb;
+static mqi_db_t   *dbs;
+mdb_handle_map_t  *table_handle;
+mdb_hash_t        *table_name_hash;
+mdb_handle_map_t  *transact_handle;
+mqi_transaction_t  txstack[MQI_TXDEPTH_MAX];
+int                txdepth;
+
+
+int mqi_open(void)
+{
+    if (!ndb && !dbs) {
+        if (!(dbs = calloc(MAX_DB, sizeof(mqi_db_t)))) {
+            errno = ENOMEM;
+            return -1;
+        }
+
+        table_handle = MDB_HANDLE_MAP_CREATE();
+        table_name_hash = MDB_HASH_TABLE_CREATE(varchar, 256);
+        
+        transact_handle = MDB_HANDLE_MAP_CREATE();
+        
+        if (db_register("MurphyDB", MQI_TEMPORARY, mdb_backend_init()) < 0) {
+            errno = EIO;
+            return -1;
+        }
+    }
+
+    return 0;
+}
+
+int mqi_close(void)
+{
+    int i;
+
+    if (ndb > 0 && dbs) {
+        for (i = 0; i < ndb; i++)
+            free((void *)dbs[i].engine);
+
+        free(dbs);
+
+        MDB_HANDLE_MAP_DESTROY(table_handle);
+        MDB_HASH_TABLE_DESTROY(table_name_hash);
+        MDB_HANDLE_MAP_DESTROY(transact_handle);
+
+        table_handle = NULL;
+        table_name_hash = NULL;
+        transact_handle = NULL;
+        
+        dbs = NULL;
+        ndb = 0;
+    }
+
+    return 0;
+}
+
+int mqi_show_tables(uint32_t flags, char **buf, int len)
+{
+    mqi_handle_t h;
+    mqi_table_t *tbl;
+    mqi_db_t *db;
+    void *data;
+    char *name;
+    void *cursor;
+    int   i = 0;
+    int   j;
+
+    MDB_CHECKARG(buf && len > 0, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+
+    MDB_HASH_TABLE_FOR_EACH_WITH_KEY(table_name_hash, data, name, cursor) {
+        if (i >= len) {
+            errno = EOVERFLOW;
+            return -1;
+        }
+
+        if ((h = data - NULL) == MQI_HANDLE_INVALID)
+            continue;
+
+        if (!(tbl = mdb_handle_get_data(table_handle, h)) || !(db = tbl->db))
+            continue;
+
+        if (!(DB_TYPE(db) & flags))
+            continue;
+
+        for (j = 0; j < i;  j++) {
+            if (strcasecmp(name, buf[j]) < 0) {
+                memmove(buf + (j+1), buf + j, sizeof(char *) * (i-j));
+                break;
+            }
+        }
+        buf[j] = name;
+
+        i++;
+    }
+
+    return i;
+}
+
+mqi_handle_t mqi_begin_transaction(void)
+{
+    mqi_transaction_t *tx;
+    mqi_db_t          *db;
+    mqi_db_functbl_t  *ftb;
+    uint32_t           depth;
+    int                i;
+
+    MDB_PREREQUISITE(dbs && ndb > 0 && transact_handle, MQI_HANDLE_INVALID);
+    MDB_ASSERT(txdepth < MQI_TXDEPTH_MAX - 1, EOVERFLOW, MQI_HANDLE_INVALID);
+
+    depth = txdepth++;
+    tx = txstack + depth;
+
+    TX_USEID_INCREMENT(tx->useid);
+
+    for (i = 0; i < ndb; i++) {
+        db  = dbs + i;
+        ftb = db->functbl;
+        tx->txid[i] = ftb->begin_transaction();
+    }
+    
+    return TX_HANDLE(tx->useid, depth);
+}
+
+
+int mqi_commit_transaction(mqi_handle_t h)
+{
+    uint32_t           depth = TX_DEPTH(h);
+    uint32_t           useid = TX_USEID(h);
+    mqi_transaction_t *tx;
+    mqi_db_t          *db;
+    mqi_db_functbl_t  *ftb;
+    int                err;
+    int                i;
+
+    MDB_CHECKARG(h != MQI_HANDLE_INVALID && depth < MQI_TXDEPTH_MAX, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+    MDB_ASSERT(depth == txdepth - 1, EBADSLT, -1);
+
+    tx = txstack + depth;
+
+    MDB_ASSERT(tx->useid == useid, EBADSLT, -1);
+
+    for (i = 0, err = 0;  i < ndb;  i++) {
+        db  = dbs + i;
+        ftb = db->functbl;
+
+        if (ftb->commit_transaction(tx->txid[i]) < 0)
+            err = -1;
+    }
+
+    return err;
+}
+
+int mqi_rollback_transaction(mqi_handle_t h)
+{
+    uint32_t           depth = TX_DEPTH(h);
+    uint32_t           useid = TX_USEID(h);
+    mqi_transaction_t *tx;
+    mqi_db_t          *db;
+    mqi_db_functbl_t  *ftb;
+    int                err;
+    int                i;
+
+    MDB_CHECKARG(h != MQI_HANDLE_INVALID && depth < MQI_TXDEPTH_MAX, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+    MDB_ASSERT(txdepth > 0 && depth == txdepth - 1, EBADSLT, -1);
+
+    tx = txstack + depth;
+
+    MDB_ASSERT(tx->useid == useid, EBADSLT, -1);
+
+    for (i = 0, err = 0;  i < ndb;  i++) {
+        db  = dbs + i;
+        ftb = db->functbl;
+
+        if (ftb->rollback_transaction(tx->txid[i]) < 0)
+            err = -1;
+    }
+
+    return err;
+}
+
+mqi_handle_t mqi_get_transaction_handle(void)
+{
+    uint32_t           depth;
+    mqi_transaction_t *tx;
+
+    MDB_CHECKARG(txdepth > 0, MQI_HANDLE_INVALID);
+    MDB_PREREQUISITE(dbs && ndb > 0, MQI_HANDLE_INVALID);
+
+    depth = txdepth - 1;
+    tx = txstack + depth;
+    
+    return TX_HANDLE(tx->useid, depth);
+}
+
+mqi_handle_t mqi_create_table(char *name,
+                              uint32_t flags,
+                              char **index_columns,
+                              mqi_column_def_t *cdefs)
+{
+    mqi_db_t         *db;
+    mqi_db_functbl_t *ftb;
+    mqi_table_t      *tbl = NULL;
+    mqi_handle_t      h = MQI_HANDLE_INVALID;
+    char             *namedup = NULL;
+    int               i;
+
+    MDB_CHECKARG(name && cdefs, MQI_HANDLE_INVALID);
+    MDB_PREREQUISITE(dbs && ndb > 0, MQI_HANDLE_INVALID);
+
+    for (i = 0, ftb = NULL;  i < ndb;  i++) {
+        db = dbs + i;
+
+        if ((DB_TYPE(db) & flags) != 0) {
+            ftb = db->functbl;
+            break;
+        }
+    }
+
+    MDB_ASSERT(ftb, ENOENT, MQI_HANDLE_INVALID);
+
+    if(!(tbl = calloc(1, sizeof(mqi_table_t))))
+        return MQI_HANDLE_INVALID;
+
+    tbl->db = db;
+    tbl->handle = NULL;
+    
+    if (!(namedup = strdup(name)))
+        goto cleanup;
+
+    if (!(tbl->handle = ftb->create_table(name, index_columns, cdefs)))
+        goto cleanup;
+
+    if ((h = mdb_handle_add(table_handle, tbl)) == MQI_HANDLE_INVALID)
+        goto cleanup;
+
+    if (mdb_hash_add(table_name_hash, 0,namedup, NULL + h) < 0) {
+        mdb_handle_delete(table_handle, h);
+        h = MQI_HANDLE_INVALID;
+    }
+
+    return h;
+
+ cleanup:
+    if (tbl) {
+        if (tbl->handle) {
+            mdb_handle_delete(table_handle, h);
+            ftb->drop_table(tbl->handle);
+        }
+        mdb_hash_delete(table_name_hash, 0,name);
+        free(namedup);
+        free(tbl);
+    }
+     
+    return MDB_HANDLE_INVALID;
+}
+
+
+int mqi_create_index(mqi_handle_t h, char **index_columns)
+{
+    mqi_db_functbl_t *ftb;
+    void             *tbl;
+
+    MDB_CHECKARG(h != MDB_HANDLE_INVALID && index_columns, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+
+    GET_TABLE(tbl, ftb, h, -1);
+
+    return ftb->create_index(tbl, index_columns);
+}
+
+int mqi_drop_table(mqi_handle_t h)
+{
+    mqi_table_t      *tbl;
+    mqi_db_functbl_t *ftb;
+    char             *name;
+    void             *data;
+    void             *cursor;
+    int               sts;
+
+    MDB_CHECKARG(h != MDB_HANDLE_INVALID, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+
+    if (!(tbl = mdb_handle_delete(table_handle, h)))
+        return -1;
+
+    ftb = tbl->db->functbl;
+
+    MDB_HASH_TABLE_FOR_EACH_WITH_KEY_SAFE(table_name_hash, data,name, cursor) {
+        if ((mqi_handle_t)(data - NULL) == h) {
+            mdb_hash_delete(table_name_hash, 0,name);
+            sts = ftb->drop_table(tbl->handle);
+            free(name);
+            free(tbl);
+            return sts;
+        }
+    }
+
+    return -1;
+}
+
+int mqi_describe(mqi_handle_t h, mqi_column_def_t *defs, int len)
+{
+    mqi_db_functbl_t *ftb;
+    void             *tbl;
+
+    MDB_CHECKARG(h != MDB_HANDLE_INVALID && defs && len > 0, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+
+    GET_TABLE(tbl, ftb, h, -1);
+
+    return ftb->describe(tbl, defs, len);
+}
+
+int mqi_insert_into(mqi_handle_t         h,
+                    int                 ignore,
+                    mqi_column_desc_t  *cds,
+                    void              **data)
+{
+    mqi_db_functbl_t *ftb;
+    void             *tbl;
+
+    MDB_CHECKARG(h != MDB_HANDLE_INVALID && cds && data && data[0], -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+
+    GET_TABLE(tbl, ftb, h, -1);
+
+    return ftb->insert_into(tbl, ignore, cds, data);
+} 
+
+int mqi_select(mqi_handle_t       h,
+               mqi_cond_entry_t  *cond,
+               mqi_column_desc_t *cds,
+               void              *rows,
+               int                rowsize,
+               int                dim)
+{
+    mqi_db_functbl_t *ftb;
+    void             *tbl;
+
+    MDB_CHECKARG(h != MDB_HANDLE_INVALID && cds &&
+                 rows && rowsize > 0 && dim > 0, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+    
+    GET_TABLE(tbl, ftb, h, -1);
+
+    return ftb->select(tbl, cond, cds, rows, rowsize, dim);
+}
+
+int mqi_select_by_index(mqi_handle_t       h,
+                        mqi_variable_t    *idxvars,
+                        mqi_column_desc_t *cds,
+                        void              *result)
+{
+    mqi_db_functbl_t *ftb;
+    void             *tbl;
+
+    MDB_CHECKARG(h != MDB_HANDLE_INVALID && idxvars && cds && result, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+    
+    GET_TABLE(tbl, ftb, h, -1);
+
+    return ftb->select_by_index(tbl, idxvars, cds, result);
+}
+
+int mqi_update(mqi_handle_t       h,
+               mqi_cond_entry_t  *cond,
+               mqi_column_desc_t *cds,
+               void              *data)
+{
+    mqi_db_functbl_t *ftb;
+    void             *tbl;
+
+    MDB_CHECKARG(h != MDB_HANDLE_INVALID && cds && data, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+
+    GET_TABLE(tbl, ftb, h, -1);
+
+    return ftb->update(tbl, cond, cds, data);
+}
+
+int mqi_delete_from(mqi_handle_t h, mqi_cond_entry_t *cond)
+{
+    mqi_db_functbl_t *ftb;
+    void             *tbl;
+
+    MDB_CHECKARG(h != MDB_HANDLE_INVALID, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+
+    GET_TABLE(tbl, ftb, h, -1);
+
+    return ftb->delete_from(tbl, cond);
+}
+
+mqi_handle_t mqi_get_table_handle(char *table_name)
+{
+    MDB_CHECKARG(table_name, MQI_HANDLE_INVALID);
+    MDB_PREREQUISITE(dbs && ndb > 0, MQI_HANDLE_INVALID);
+
+    return mdb_hash_get_data(table_name_hash, 0,table_name) - NULL;
+}
+
+
+int mqi_get_column_index(mqi_handle_t h, char *column_name)
+{
+    mqi_db_functbl_t *ftb;
+    void             *tbl;
+
+    MDB_CHECKARG(h != MDB_HANDLE_INVALID && column_name, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+
+    GET_TABLE(tbl, ftb, h, -1);
+
+    return ftb->get_column_index(tbl, column_name);
+}
+
+int mqi_get_table_size(mqi_handle_t h)
+{
+    mqi_db_functbl_t *ftb;
+    void             *tbl;
+
+    MDB_CHECKARG(h != MDB_HANDLE_INVALID, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+
+    GET_TABLE(tbl, ftb, h, -1);
+
+    return  ftb->get_table_size(tbl);
+}
+
+char *mqi_get_column_name(mqi_handle_t h, int colidx)
+{
+    mqi_db_functbl_t *ftb;
+    void             *tbl;
+
+    MDB_CHECKARG(h != MDB_HANDLE_INVALID && colidx >= 0, NULL);
+    MDB_PREREQUISITE(dbs && ndb > 0, NULL);
+
+    GET_TABLE(tbl, ftb, h, NULL);
+
+    return  ftb->get_column_name(tbl, colidx);
+}
+
+mqi_data_type_t mqi_get_column_type(mqi_handle_t h, int colidx)
+{
+    mqi_db_functbl_t *ftb;
+    void             *tbl;
+
+    MDB_CHECKARG(h != MDB_HANDLE_INVALID && colidx >= 0, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+
+    GET_TABLE(tbl, ftb, h, -1);
+
+    return  ftb->get_column_type(tbl, colidx);
+}
+
+int mqi_get_column_size(mqi_handle_t h, int colidx)
+{
+    mqi_db_functbl_t *ftb;
+    void             *tbl;
+
+    MDB_CHECKARG(h != MDB_HANDLE_INVALID && colidx >= 0, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+
+    GET_TABLE(tbl, ftb, h, -1);
+
+    return  ftb->get_column_size(tbl, colidx);
+}
+
+int mqi_print_rows(mqi_handle_t h, char *buf, int len)
+{
+    mqi_db_functbl_t *ftb;
+    void             *tbl;
+
+    MDB_CHECKARG(h != MDB_HANDLE_INVALID && buf && len > 0, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+
+    GET_TABLE(tbl, ftb, h, -1);
+
+    return ftb->print_rows(tbl, buf, len);
+}
+
+
+
+static int db_register(const char       *engine,
+                       uint32_t          flags,
+                       mqi_db_functbl_t *functbl)
+{
+    mqi_db_t *db;
+    int i;
+
+    MDB_CHECKARG(engine && engine[0] && functbl, -1);
+    MDB_PREREQUISITE(dbs, -1);
+
+    if (ndb + 1 >= MAX_DB) {
+        errno = EOVERFLOW;
+        return -1;
+    }
+
+    for (i = 0; i < ndb;  i++) {
+        if (!strcmp(engine, dbs[i].engine)) {
+            errno = EEXIST;
+            return -1;
+        }
+    }
+
+    db = dbs + ndb++;
+
+    db->engine  = strdup(engine);
+    db->flags   = flags;
+    db->functbl = functbl;
+    
+    return 0;
+}
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mql/Makefile.am b/src/murphy-db/mql/Makefile.am
new file mode 100644 (file)
index 0000000..3ede9ab
--- /dev/null
@@ -0,0 +1,26 @@
+pkglib_LTLIBRARIES = libmql.la
+
+PARSER_PREFIX   = yy_mql_
+AM_YFLAGS       = -p $(PARSER_PREFIX)
+LEX_OUTPUT_ROOT = ./lex.$(PARSER_PREFIX)
+
+libmql_la_CFLAGS = -I.. -I../include
+
+libmql_la_SOURCES = ../include/murphy-db/mql.h \
+                    ../include/murphy-db/statement.h \
+                    ../include/murphy-db/result.h \
+                    mql-scanner.l mql-parser.y \
+                    statement.c result.c
+
+mql-parser.h mql-parser.c: mql-parser.y
+       $(YACCCOMPILE) $<
+       mv -f y.tab.h mql-parser.h
+       mv -f y.tab.c mql-parser.c
+
+mql-scanner.c: mql-scanner.l mql-parser.h
+       $(LEXCOMPILE) $<
+       mv lex.$(PARSER_PREFIX).c $@
+
+
+clean-local::
+       rm -f *~ mql-scanner.c mql-parser.[hc] *.tab.[hc]
diff --git a/src/murphy-db/mql/mql-parser.y b/src/murphy-db/mql/mql-parser.y
new file mode 100644 (file)
index 0000000..257757d
--- /dev/null
@@ -0,0 +1,1295 @@
+%{
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <alloca.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+
+#include <murphy-db/assert.h>
+#include <murphy-db/mqi.h>
+#include <murphy-db/mql.h>
+
+#define MQL_SUCCESS                                                     \
+    do {                                                                \
+        if (mode == mql_mode_exec)                                      \
+            result = mql_result_success_create();                       \
+    } while (0)
+
+#define MQL_ERROR(code, fmt...)                                         \
+    do {                                                                \
+        switch (mode) {                                                 \
+        case mql_mode_exec:                                             \
+            result = mql_result_error_create(code, fmt);                \
+            break;                                                      \
+        case mql_mode_precompile:                                       \
+            errno = code;                                               \
+            free(statement);                                            \
+            statement = NULL;                                           \
+            break;                                                      \
+        case mql_mode_parser:                                           \
+            fprintf(mqlout, "%s:%d: error: ", file, yy_mql_lineno);     \
+            fprintf(mqlout, fmt);                                       \
+            fprintf(mqlout, "\n");                                      \
+            break;                                                      \
+        }                                                               \
+        YYERROR;                                                        \
+    } while (0)
+
+
+#define SET_INPUT(t,v)                                                  \
+    input_t *input;                                                     \
+    if (ninput >= MQI_COLUMN_MAX)                                       \
+        MQL_ERROR(EOVERFLOW, "Too many input values\n");                \
+    input = inputs + ninput++;                                          \
+    input->type = mqi_##t;                                              \
+    input->flags = 0;                                                   \
+    input->value.t = (v)
+
+
+typedef enum {
+    mql_mode_parser,
+    mql_mode_exec,
+    mql_mode_precompile,
+} mql_mode_t;
+
+typedef struct {
+    mqi_data_type_t      type;
+    uint32_t             flags;
+    union {
+        char *varchar;
+        int32_t integer;
+        uint32_t unsignd;
+        double floating;
+    }                    value;
+} input_t;
+
+extern int yy_mql_lineno;
+extern int yy_mql_lex(void);
+
+
+void yy_mql_error(const char *);
+
+static void print_query_result(mqi_column_desc_t *, mqi_data_type_t *,
+                               int *, int, int, void *);
+
+mqi_handle_t table;
+uint32_t     table_flags;
+
+
+static mqi_column_def_t   coldefs[MQI_COLUMN_MAX + 1];
+static mqi_column_def_t  *coldef = coldefs;
+
+static char              *colnams[MQI_COLUMN_MAX + 1];
+static int                ncolnam;
+
+static mqi_cond_entry_t   conds[MQI_COND_MAX + 1];
+static mqi_cond_entry_t  *cond = conds;
+static int                binds;
+
+static input_t            inputs[MQI_COLUMN_MAX];
+static int                ninput;
+
+static mqi_column_desc_t  coldescs[MQI_COLUMN_MAX + 1];
+static int                ncoldesc;
+
+static char    *strs[256];
+static int      nstr;
+
+static int32_t  ints[256];
+static int      nint;
+
+static uint32_t uints[32];
+static int      nuint;
+
+static double   floats[256];
+static int      nfloat;
+
+static mql_mode_t mode;
+
+static mql_statement_t   *statement;
+
+static mql_result_type_t  rtype;
+static mql_result_t      *result;
+
+static char        *file;
+static const char  *mqlbuf;
+static int          mqlin;
+static FILE        *mqlout;
+
+%}
+
+%union {
+    mqi_data_type_t  type;
+    char            *string;
+    long long int    number;
+    double           floating;
+    int              integer;
+};
+
+
+%defines
+
+%token <string>   TKN_SHOW
+%token <string>   TKN_BEGIN
+%token <string>   TKN_COMMIT
+%token <string>   TKN_ROLLBACK
+%token <string>   TKN_TRANSACTION
+%token <string>   TKN_CREATE
+%token <string>   TKN_UPDATE
+%token <string>   TKN_REPLACE
+%token <string>   TKN_DELETE
+%token <string>   TKN_DROP
+%token <string>   TKN_DESCRIBE
+%token <string>   TKN_TABLE
+%token <string>   TKN_TABLES
+%token <string>   TKN_INDEX
+%token <string>   TKN_INSERT
+%token <string>   TKN_SELECT
+%token <string>   TKN_INTO
+%token <string>   TKN_FROM
+%token <string>   TKN_WHERE
+%token <string>   TKN_VALUES
+%token <string>   TKN_SET
+%token <string>   TKN_ON
+%token <string>   TKN_OR
+%token <string>   TKN_PERSISTENT
+%token <string>   TKN_TEMPORARY
+%token <string>   TKN_VARCHAR
+%token <string>   TKN_INTEGER
+%token <string>   TKN_UNSIGNED
+%token <string>   TKN_REAL
+%token <string>   TKN_BLOB
+%token <integer>  TKN_PARAMETER
+%token <string>   TKN_LOGICAL_AND
+%token <string>   TKN_LOGICAL_OR
+%token <string>   TKN_LESS
+%token <string>   TKN_LESS_OR_EQUAL
+%token <string>   TKN_EQUAL
+%token <string>   TKN_GREATER_OR_EQUAL
+%token <string>   TKN_GREATER
+%token <string>   TKN_NOT
+%token <string>   TKN_LEFT_PAREN
+%token <string>   TKN_RIGHT_PAREN
+%token <string>   TKN_COMMA
+%token <string>   TKN_SEMICOLON
+%token <string>   TKN_PLUS
+%token <string>   TKN_MINUS
+%token <string>   TKN_STAR
+%token <string>   TKN_SLASH
+%token <number>   TKN_NUMBER
+%token <floating> TKN_FLOATING
+%token <string>   TKN_IDENTIFIER
+%token <string>   TKN_QUOTED_STRING
+
+%type <integer>   insert
+%type <integer>   insert_or_replace
+%type <integer>   insert_option
+
+%type <integer>   varchar
+%type <integer>   blob
+%type <integer>   sign
+
+%type <floating>  floating_value
+
+%start statement_list
+
+%code requires {
+    #include <murphy-db/mqi.h>
+    #include <murphy-db/mql.h>
+
+    int yy_mql_input(void *, unsigned);
+
+    mql_statement_t *mql_make_show_tables_statement(uint32_t);
+    mql_statement_t *mql_make_describe_statement(mqi_handle_t);
+    mql_statement_t *mql_make_insert_statement(mqi_handle_t, int, int,
+                                               mqi_data_type_t*,
+                                               mqi_column_desc_t*, void*);
+    mql_statement_t *mql_make_update_statement(mqi_handle_t, int,
+                                               mqi_cond_entry_t *, int,
+                                               mqi_data_type_t *,
+                                               mqi_column_desc_t *, void *);
+    mql_statement_t *mql_make_delete_statement(mqi_handle_t, int,
+                                               mqi_cond_entry_t *);
+    mql_statement_t *mql_make_select_statement(mqi_handle_t, int, int,
+                                               mqi_cond_entry_t *, int,
+                                               char **, mqi_data_type_t *,
+                                               int *, mqi_column_desc_t *);
+
+    mql_result_t *mql_result_success_create(void);
+    mql_result_t *mql_result_error_create(int, const char *, ...);
+    mql_result_t *mql_result_columns_create(int, mqi_column_def_t *);
+    mql_result_t *mql_result_rows_create(int, mqi_column_desc_t*,
+                                         mqi_data_type_t*,int*,int,int,void*);
+    mql_result_t *mql_result_string_create_table_list(int, char **);
+    mql_result_t *mql_result_string_create_column_list(int, mqi_column_def_t*);
+    mql_result_t *mql_result_string_create_row_list(int, char **,
+                                                    mqi_column_desc_t *,
+                                                    mqi_data_type_t *, int *,
+                                                    int, int, void *);
+    mql_result_t *mql_result_list_create(mqi_data_type_t, int, void *);
+}
+
+
+%%
+
+statement_list:
+  statement
+| statement_list semicolon statement
+;
+
+semicolon: TKN_SEMICOLON {
+    if (mode != mql_mode_parser) {
+        result = mql_result_error_create(EINVAL, "multiple MQL statements");
+        YYERROR;
+    }
+};
+
+statement:
+  TKN_SHOW  show_statement
+| TKN_CREATE create_statement
+| TKN_DROP drop_statement
+| begin_statement
+| commit_statement
+| rollback_statement
+| describe_statement
+| insert_statement
+| update_statement
+| delete_statement
+| select_statement
+| error
+;
+
+/***************************
+ *
+ * Show statement
+ *
+ */
+show_statement:
+  show_tables
+;
+
+show_tables: table_flags TKN_TABLES {
+    char  *names[4096];
+    int    n;
+    
+    if (mode == mql_mode_precompile)
+        statement = mql_make_show_tables_statement(table_flags);
+    else {
+        if ((n = mqi_show_tables(table_flags, names,MQI_DIMENSION(names))) < 0)
+            MQL_ERROR(errno, "can't show tables: %s", strerror(errno));
+        else {
+            if (mode == mql_mode_exec) {
+                switch (rtype) {
+                case mql_result_string:
+                    result = mql_result_string_create_table_list(n, names);
+                    break;
+                case mql_result_list:
+                    result = mql_result_list_create(mqi_string,n,(void*)names);
+                    break;
+                default:
+                    result = mql_result_error_create(EINVAL,
+                                                     "can't show tables: %s",
+                                                     strerror(EINVAL));
+                    break;
+                }
+            }
+            else {
+                mql_result_t *r = mql_result_string_create_table_list(n,names);
+
+                fprintf(mqlout, "%s", mql_result_string_get(r));
+
+                mql_result_free(r);
+            }
+        }
+    }
+};
+
+/***********************************
+ *
+ * Create statement
+ *
+ */
+create_statement:
+  create_table table_definition
+| create_index index_definition
+; 
+
+/* create table */
+
+create_table: table_flags TKN_TABLE {
+    coldef = coldefs;
+    
+    if (table_flags == MQI_ANY)
+        table_flags = MQI_TEMPORARY;
+};
+
+
+
+table_definition: TKN_IDENTIFIER TKN_LEFT_PAREN column_defs TKN_RIGHT_PAREN {
+    if (mqi_create_table($1, table_flags, NULL, coldefs) < 0)
+        MQL_ERROR(errno, "Can't create table: %s\n", strerror(errno));
+    else
+        MQL_SUCCESS;
+};
+
+column_defs:
+  column_def
+| column_defs TKN_COMMA column_def
+;
+
+column_def: column_name column_type {
+    memset(++coldef, 0, sizeof(mqi_column_def_t));
+};
+
+column_name: TKN_IDENTIFIER {
+    if ((coldef - coldefs) >= MQI_COLUMN_MAX) {
+        MQL_ERROR(EOVERFLOW, "Too many columns. Max %d columns allowed\n",
+                  MQI_COLUMN_MAX);
+    }
+
+    coldef->name = $1;
+};
+
+column_type:
+  varchar       { coldef->type = mqi_varchar;   coldef->length = $1; }
+| TKN_INTEGER   { coldef->type = mqi_integer;   coldef->length = 0;  }
+| TKN_UNSIGNED  { coldef->type = mqi_unsignd;   coldef->length = 0;  }
+| TKN_FLOATING  { coldef->type = mqi_floating;  coldef->length = 0;  }
+| blob          { coldef->type = mqi_blob;      coldef->length = $1; }
+;
+
+varchar: TKN_VARCHAR TKN_LEFT_PAREN TKN_NUMBER TKN_RIGHT_PAREN {
+    $$ = (int)$3;
+};
+
+blob: TKN_BLOB TKN_LEFT_PAREN TKN_NUMBER TKN_RIGHT_PAREN {
+    $$ = (int)$3;
+};
+
+/* create index */
+
+create_index: TKN_INDEX {
+    ncolnam = 0;
+};
+
+index_definition: TKN_ON table_name TKN_LEFT_PAREN column_list TKN_RIGHT_PAREN
+{
+    colnams[ncolnam] = NULL;
+
+    if (mqi_create_index(table, colnams) < 0)
+        MQL_ERROR(errno, "failed to create index: %s", strerror(errno));
+    else
+        MQL_SUCCESS;
+};
+
+
+/***********************************
+ *
+ * Drop statement
+ *
+ */
+drop_statement:
+  drop_table
+| drop_index
+; 
+
+/* drop table */
+
+drop_table: TKN_TABLE  table_name {
+    if (mqi_drop_table(table) < 0)
+        MQL_ERROR(errno, "failed to drop table: %s", strerror(errno));
+    else
+        MQL_SUCCESS;
+}
+;
+
+
+/* drop index */
+
+drop_index: TKN_INDEX {
+};
+
+
+/***********************************
+ *
+ * Begin/Commit/Rollback statement
+ *
+ */
+begin_statement: TKN_BEGIN transaction {
+    if (mqi_begin_transaction() == MQI_HANDLE_INVALID)
+        MQL_ERROR(errno, "Can't start transaction: %s", strerror(errno));
+    else
+        MQL_SUCCESS;
+};
+
+commit_statement: TKN_COMMIT transaction {
+    mqi_handle_t txh = mqi_get_transaction_handle();
+
+    if (txh == MQI_HANDLE_INVALID || mqi_commit_transaction(txh) < 0)
+        MQL_ERROR(errno, "failed to commit transaction: %s", strerror(errno));
+    else
+        MQL_SUCCESS;
+};
+
+rollback_statement: TKN_ROLLBACK transaction {
+    mqi_handle_t txh = mqi_get_transaction_handle();
+
+    if (txh == MQI_HANDLE_INVALID || mqi_rollback_transaction(txh) < 0)
+        MQL_ERROR(errno, "can't rollback transaction: %s", strerror(errno));
+    else
+        MQL_SUCCESS;
+};
+
+
+
+/***********************************
+ *
+ * Describe statement
+ *
+ */
+describe_statement: TKN_DESCRIBE table_name {
+    mqi_column_def_t defs[MQI_COLUMN_MAX];
+    int              n;
+
+    if (mode == mql_mode_precompile)
+        statement = mql_make_describe_statement(table);
+    else {
+        if ((n = mqi_describe(table, defs, MQI_COLUMN_MAX)) < 0)
+            MQL_ERROR(errno, "can't describe table: %s", strerror(errno));
+        else {
+            if (mode == mql_mode_exec) {
+                switch (rtype) {
+                case mql_result_columns:
+                    result = mql_result_columns_create(n, defs);
+                    break;
+                case mql_result_string:
+                    result = mql_result_string_create_column_list(n, defs);
+                    break;
+                default:
+                    result = mql_result_error_create(EINVAL, "describe failed:"
+                                                     " invalid result type %d",
+                                                     rtype);
+                    break;
+                }
+            }
+            else {
+                mql_result_t *r = mql_result_string_create_column_list(n,defs);
+
+                fprintf(mqlout, "%s", mql_result_string_get(r));
+
+                mql_result_free(r);
+            }
+        }
+    }
+};
+
+/***********************************
+ *
+ * Insert statement
+ *
+ */
+insert_statement: insert table_name insert_columns TKN_VALUES insert_values {
+    void              *row[2];
+    char              *col;
+    mqi_column_desc_t *cd;
+    mqi_data_type_t    coltypes[MQI_COLUMN_MAX + 1];
+    input_t           *inp;
+    mqi_data_type_t    type;
+    int                cindex;
+    int                err;
+    int                i;
+
+    if (!ncolnam) {
+        while ((colnams[ncolnam] = mqi_get_column_name(table, ncolnam)))
+            ncolnam++;
+    }
+
+    if (ncolnam != ninput)
+        MQL_ERROR(EINVAL, "unbalanced set of columns and values");
+
+    for (i = 0, err = 0; i < ncolnam; i++) {
+        col = colnams[i];
+        cd  = coldescs + i;
+        inp = inputs + i;
+
+        if ((cindex = mqi_get_column_index(table, col)) < 0) {
+            MQL_ERROR(ENOENT, "know nothing about '%s'", col);
+            err = 1;
+            continue;
+        }
+
+        type = coltypes[i] = mqi_get_column_type(table, cindex);
+
+        if (type != inp->type) {
+            if (type != mqi_integer ||
+                inp->type != mqi_unsignd ||
+                inp->value.unsignd > INT32_MAX)
+            {
+                MQL_ERROR(EINVAL, "mismatching column and value type for '%s'",
+                          col);
+                err = 1;
+                continue;
+            }
+        }
+
+        cd->cindex = cindex;
+        cd->offset = (void *)&inp->value - (void *)inputs;
+    }
+
+    cd = coldescs + i;
+    cd->cindex = -1;
+    cd->offset = -1;
+
+
+    if (mode == mql_mode_precompile) {
+        statement = mql_make_insert_statement(table, $1, ncolnam, coltypes,
+                                              coldescs, inputs);
+    }
+    else {
+        row[0] = (void *)inputs;
+        row[1] = NULL;
+
+        if (err || mqi_insert_into(table, $1, coldescs, row) < 0)
+            MQL_ERROR(errno, "insert failed: %s\n", strerror(errno));
+        else
+            MQL_SUCCESS;
+    }
+};
+
+
+insert: insert_or_replace {
+      table = MQI_HANDLE_INVALID;
+      ncolnam = 0;
+      ninput = 0;
+      ncoldesc = 0;
+      $$ = $1;
+};
+
+insert_or_replace:
+  TKN_INSERT insert_option TKN_INTO  { $$ = $2; }
+| TKN_REPLACE TKN_INTO               { $$ = 1;  } 
+;
+
+insert_option:
+   /* no option */     { $$ = 0; }
+| TKN_OR TKN_REPLACE   { $$ = 1; }
+/*
+| TKN_IGNORE           { $$ = 1; }
+*/
+;
+
+
+insert_columns: 
+  /* all columns: leaves ncolnam as zero */
+| TKN_LEFT_PAREN column_list TKN_RIGHT_PAREN
+;
+
+insert_values: TKN_LEFT_PAREN input_value_list TKN_RIGHT_PAREN;
+
+input_value_list:
+  input_value
+| input_value_list TKN_COMMA input_value
+; 
+
+
+
+/***********************************
+ *
+ * Update statement
+ *
+ */
+update_statement: update table_name TKN_SET assignment_list where_clause {
+    mqi_column_desc_t *cd    = coldescs + ninput;
+    mqi_cond_entry_t  *where = (cond == conds) ? NULL : conds;
+    mqi_data_type_t    coltypes[MQI_COLUMN_MAX + 1];
+    int                i;
+
+    if (!ninput)
+        MQL_ERROR(ENOMEDIUM, "No column to update");
+
+    cd->cindex = -1;
+    cd->offset = -1;
+
+    if (mode == mql_mode_precompile) {
+        for (i = 0;  i < ninput; i++)
+            coltypes[i] = inputs[i].type;
+
+        statement = mql_make_update_statement(table, cond - conds, conds,
+                                              ninput, coltypes, coldescs,
+                                              inputs);
+    }
+    else {
+        if (mqi_update(table, where, coldescs, inputs) < 0)
+            MQL_ERROR(errno, "update failed: %s", strerror(errno));
+        else
+            MQL_SUCCESS;
+    }
+};
+
+update: TKN_UPDATE {
+      table = MQI_HANDLE_INVALID;
+      ninput = 0;
+      ncoldesc = 0;
+      nstr = 0;
+      nint = 0;
+      nuint = 0;
+      nfloat = 0;
+      cond = conds;
+      binds = 0;
+};
+
+
+assignment_list:
+  assignment
+| assignment_list TKN_COMMA assignment
+;
+
+assignment: TKN_IDENTIFIER TKN_EQUAL input_value {
+    int                i   = ninput - 1;
+    input_t           *inp = inputs + i;
+    mqi_column_desc_t *cd  = coldescs + i;
+    int                cindex;
+    int                offset;
+    mqi_data_type_t    type;
+
+    if ((cindex = mqi_get_column_index(table, $1)) < 0)
+        MQL_ERROR(ENOENT, "know nothing about '%s'", $1);
+    if ((inp->flags & MQL_BINDABLE))
+        offset = -(MQL_BIND_INDEX(inp->flags) + 1);
+    else {
+        if ((type = mqi_get_column_type(table, cindex)) != inp->type) {
+            if (type != mqi_integer ||
+                inp->type != mqi_unsignd ||
+                inp->value.unsignd > INT32_MAX)
+            {
+                MQL_ERROR(EINVAL, "mismatching column and value type "
+                          "for '%s'",$1);
+            }
+        }
+        offset = (void *)&inp->value - (void *)inputs;
+    }
+
+    cd->cindex = cindex;
+    cd->offset = offset;
+};
+
+
+
+/***********************************
+ *
+ * Delete statement
+ *
+ */
+delete_statement: delete table_name where_clause {
+    mqi_cond_entry_t *where = (cond == conds) ? NULL : conds;
+
+    if (mode == mql_mode_precompile)
+        statement = mql_make_delete_statement(table, cond - conds, where);
+    else {
+        if (mqi_delete_from(table, where) < 0)
+            MQL_ERROR(errno, "delete failed: %s", strerror(errno));
+        else
+            MQL_SUCCESS;
+    }
+};
+
+delete: TKN_DELETE TKN_FROM {
+    table = MQI_HANDLE_INVALID;
+    nstr = 0;
+    nint = 0;
+    nuint = 0;
+    nfloat = 0;
+    cond = conds;
+    binds = 0;
+};
+
+/***********************************
+ *
+ * Select statement
+ *
+ */
+select_statement: select columns TKN_FROM table_name where_clause {
+    mqi_column_desc_t *cd;
+    int colsizes[MQI_COLUMN_MAX + 1];
+    int colsize;
+    int colidx;
+    mqi_data_type_t coltypes[MQI_COLUMN_MAX + 1];
+    mqi_data_type_t coltype;
+    mqi_cond_entry_t *where;
+    int rowsize;
+    int tsiz;
+    size_t rsiz;
+    void *rows;
+    int i, n;
+
+
+    if ((tsiz = mqi_get_table_size(table)) < 0)
+        MQL_ERROR(errno, "can't get table size: %s", strerror(errno));
+
+
+    if (!ncolnam) {
+        while ((colnams[ncolnam] = mqi_get_column_name(table, ncolnam)))
+            ncolnam++;
+    }
+
+    for (i = 0, rowsize = 0;  i < ncolnam;   i++) {
+        cd = coldescs + i;
+        
+        if ((colidx  = mqi_get_column_index(table, colnams[i])) < 0 ||
+            (colsize = mqi_get_column_size(table, colidx))      < 0 ||
+            (coltype = mqi_get_column_type(table, colidx)) == mqi_error)
+        {
+            MQL_ERROR(errno, "invalid column '%s'\n", colnams[i]);
+        }
+        cd->cindex = colidx;
+        cd->offset = rowsize;
+        
+        coltypes[i] = coltype;
+        colsizes[i] = colsize;
+        
+        switch (coltype) {
+        case mqi_varchar:   rowsize += sizeof(char *);    break;
+        case mqi_integer:   rowsize += sizeof(int32_t);   break;
+        case mqi_unsignd:   rowsize += sizeof(uint32_t);  break;
+        case mqi_floating:  rowsize += sizeof(double);    break;
+        case mqi_blob:      rowsize += sizeof(void *);    break;
+        default:                                         break;
+        }
+    } /* for */
+
+    
+    cd = coldescs + i;
+    cd->cindex = -1;
+    cd->offset = -1;
+
+    if (mode != mql_mode_precompile && !tsiz) {
+        if (mode == mql_mode_parser)
+            fprintf(mqlout, "no rows\n");
+    }
+    else {
+        rsiz  = tsiz * rowsize;
+        rows  = alloca(rsiz);
+        where = (cond == conds) ? NULL : conds;
+
+        if (mode != mql_mode_precompile) {
+            if ((n = mqi_select(table, where,coldescs, rows,rowsize,tsiz)) < 0)
+                MQL_ERROR(errno, "select failed: %s", strerror(errno));
+        }
+
+        switch (mode) {
+        case mql_mode_parser:
+            fprintf(mqlout, "Selected %d rows:\n", n);
+            print_query_result(coldescs, coltypes, colsizes, n, rowsize, rows);
+            break;
+        case mql_mode_exec:
+            if (rtype == mql_result_rows) {
+                result = mql_result_rows_create(ncolnam, coldescs, coltypes,
+                                                colsizes, n, rowsize, rows);
+            }
+            else {
+                result = mql_result_string_create_row_list(ncolnam, colnams,
+                                                           coldescs, coltypes,
+                                                           colsizes,
+                                                           n, rowsize, rows);
+            }
+            break;
+        case mql_mode_precompile:
+            statement = mql_make_select_statement(table, rowsize,
+                                                  cond - conds, where,
+                                                  ncolnam, colnams, coltypes,
+                                                  colsizes, coldescs); 
+            break;
+        }
+    }
+};
+
+
+select: TKN_SELECT {
+    table = MQI_HANDLE_INVALID;
+    ncolnam = 0;
+    nstr = 0;
+    nint = 0;
+    nuint = 0;
+    nfloat = 0;
+    cond = conds;
+    binds = 0;
+};
+
+columns:
+  TKN_STAR
+| column_list
+;
+
+/***********************************
+ *
+ * Transaction
+ *
+ */
+transaction:
+  /* no token */
+| TKN_TRANSACTION
+;
+
+/***********************************
+ *
+ * Table name
+ *
+ */
+table_name: TKN_IDENTIFIER {
+    if ((table = mqi_get_table_handle($1)) == MQI_HANDLE_INVALID)
+        MQL_ERROR(errno, "Do not know anything about '%s'", $1);
+};
+
+/***********************************
+ *
+ * Table flags
+ *
+ */
+table_flags:
+  /* no option */ { table_flags = MQI_ANY;        }
+| TKN_PERSISTENT  { table_flags = MQI_PERSISTENT; }
+| TKN_TEMPORARY   { table_flags = MQI_TEMPORARY;  }
+;
+
+/***********************************
+ *
+ * Column list
+ *
+ */
+column_list:
+  column
+| column_list TKN_COMMA column
+;
+
+column: TKN_IDENTIFIER {
+    if (ncolnam < MQI_COLUMN_MAX)
+        colnams[ncolnam++] = $1;
+    else
+        MQL_ERROR(EOVERFLOW, "Too many columns");
+};
+
+/***********************************
+ *
+ * Input value
+ *
+ */
+input_value:
+  string_input
+| integer_input
+| unsigned_input
+| floating_input
+| parameter_input
+;
+
+string_input:   TKN_QUOTED_STRING { SET_INPUT(varchar,  $1);              };
+integer_input:  sign TKN_NUMBER   { SET_INPUT(integer,  $1 * $2);         };
+unsigned_input: TKN_NUMBER        { SET_INPUT(unsignd,  $1);              };
+floating_input: TKN_FLOATING      { SET_INPUT(floating, $1);              }
+|               sign TKN_FLOATING { SET_INPUT(floating, (double)$1 * $2); };
+
+parameter_input: TKN_PARAMETER {
+    input_t *input;
+
+    if (mode != mql_mode_precompile) {
+        MQL_ERROR(EINVAL, "parameters are allowed only in "
+                  "precompilation mode");
+    }
+    if (binds >= MQL_PARAMETER_MAX) {
+        MQL_ERROR(EOVERFLOW, "number of parameters exceeds %d",
+                  MQL_PARAMETER_MAX);
+    }
+
+    input = inputs + ninput++;
+    input->type = $1;
+    input->flags = MQL_BINDABLE | MQL_BIND_INDEX(binds++);
+
+    memset(&input->value, 0, sizeof(input->value));
+};
+
+
+/***********************************
+ *
+ * Where clause
+ *
+ */
+where_clause:
+  /* no where clause  */ {
+  }
+| TKN_WHERE conditional_expression {
+    cond->type = mqi_operator;
+    cond->operator = mqi_end;
+    cond++;
+  };
+
+
+conditional_expression:
+  relational_expression
+| relational_expression logical_operator relational_expression
+;
+
+relational_expression: value relational_operator value;
+
+value: 
+  column_value
+| string_variable
+| integer_variable
+| unsigned_variable
+| floating_variable
+| parameter_value
+| expression_value
+| unary_operator value
+;
+
+column_value: TKN_IDENTIFIER {
+    int cx;
+
+    if (cond - conds >= MQI_COND_MAX)
+        MQL_ERROR(EOVERFLOW, "too complex condition");
+
+    if ((cx = mqi_get_column_index(table,$1)) < 0)
+        MQL_ERROR(ENOENT, "no column with name '%s'", $1);
+
+    cond->type = mqi_column;
+    cond->column = cx;
+    cond++;
+};
+
+string_variable: TKN_QUOTED_STRING {
+    if (cond - conds >= MQI_COND_MAX)
+        MQL_ERROR(EOVERFLOW, "too complex condition");
+    strs[nstr] = $1;
+    cond->type = mqi_variable;
+    cond->variable.flags = 0;
+    cond->variable.type = mqi_varchar;
+    cond->variable.varchar = strs + nstr++;
+    cond++;
+};
+
+integer_variable: sign TKN_NUMBER {
+    if (cond - conds >= MQI_COND_MAX)
+        MQL_ERROR(EOVERFLOW, "too complex condition");
+    ints[nint] = $1 * $2;
+    cond->type = mqi_variable;
+    cond->variable.type = mqi_integer;
+    cond->variable.integer = ints + nint++;
+    cond++;
+};
+
+unsigned_variable: TKN_NUMBER {
+    if (cond - conds >= MQI_COND_MAX)
+        MQL_ERROR(EOVERFLOW, "too complex condition");
+    uints[nuint] = $1;
+    cond->type = mqi_variable;
+    cond->variable.flags = 0;
+    cond->variable.type = mqi_unsignd;
+    cond->variable.unsignd = uints + nuint++;
+    cond++;
+};
+
+floating_variable: floating_value {
+    if (cond - conds >= MQI_COND_MAX)
+        MQL_ERROR(EOVERFLOW, "too complex condition");
+    floats[nfloat] = $1;
+    cond->type = mqi_variable;
+    cond->variable.flags = 0;
+    cond->variable.type = mqi_floating;
+    cond->variable.floating = floats + nfloat++;
+    cond++;
+};
+
+floating_value:
+  TKN_FLOATING        { return $1;              }
+| sign TKN_FLOATING   { return (double)$1 * $2; }
+
+
+parameter_value: TKN_PARAMETER {
+    if (mode != mql_mode_precompile) {
+        MQL_ERROR(EINVAL, "parameters are allowed only in "
+                  "precompilation mode");
+    }
+    if (binds >= MQL_PARAMETER_MAX) {
+        MQL_ERROR(EOVERFLOW, "number of parameters exceeds %d",
+                  MQL_PARAMETER_MAX);
+    }
+    if (cond - conds >= MQI_COND_MAX) {
+        MQL_ERROR(EOVERFLOW, "too complex condition");
+    }
+    cond->type = mqi_variable;
+    cond->variable.flags = MQL_BINDABLE | MQL_BIND_INDEX(binds++);
+    cond->variable.type = $1;
+    cond->variable.generic = NULL;
+    cond++;
+};
+
+expression_value:
+  TKN_LEFT_PAREN  {
+    if (cond - conds >= MQI_COND_MAX)
+        MQL_ERROR(EOVERFLOW, "too complex condition");
+    cond->type = mqi_operator;
+    cond->operator = mqi_begin;
+    cond++;
+  }
+  conditional_expression
+  TKN_RIGHT_PAREN {
+    if (cond - conds >= MQI_COND_MAX)
+        MQL_ERROR(EOVERFLOW, "too complex condition");
+    cond->type = mqi_operator;
+    cond->operator = mqi_end;
+    cond++;
+  }
+;
+
+
+sign:
+  TKN_PLUS  { $$ = +1; }
+| TKN_MINUS { $$ = -1; }
+;
+
+
+unary_operator:
+  TKN_NOT {
+      cond->type = mqi_operator;
+      cond->operator = mqi_not;
+      cond++;
+  }
+;
+
+relational_operator:
+  TKN_LESS {
+      cond->type = mqi_operator;
+      cond->operator = mqi_less;
+      cond++;
+  }
+| TKN_LESS_OR_EQUAL {
+    cond->type = mqi_operator;
+    cond->operator = mqi_leq;
+    cond++;
+  }
+| TKN_EQUAL {
+    cond->type = mqi_operator;
+    cond->operator = mqi_eq;
+    cond++;
+  }
+| TKN_GREATER_OR_EQUAL {
+    cond->type = mqi_operator;
+    cond->operator = mqi_geq;
+    cond++;
+  }
+| TKN_GREATER {
+    cond->type = mqi_operator;
+    cond->operator = mqi_gt;
+    cond++;
+  }
+;
+
+logical_operator:
+  TKN_LOGICAL_AND {
+      cond->type = mqi_operator;
+      cond->operator = mqi_and;
+      cond++;
+  }
+| TKN_LOGICAL_OR {
+    cond->type = mqi_operator;
+    cond->operator = mqi_or;
+    cond++;
+  }
+;
+
+
+%%
+
+
+int mql_exec_file(const char *path)
+{
+    char buf[1024];
+    int sts;
+
+    mode   = mql_mode_parser;
+    rtype  = mql_result_unknown;
+    mqlbuf = NULL;
+    mqlout = stdout;
+    
+    if (!path) {
+        mqlin = fileno(stdin);
+        sts = yy_mql_parse() ? -1 : 0;
+    }
+    else {
+        strncpy(buf, path, sizeof(buf));
+        buf[sizeof(buf)-1] = '\0';
+        
+        file = basename(buf);
+
+        if ((mqlin = open(path, O_RDONLY)) < 0) {
+            sts = -1;
+            fprintf(mqlout, "could not open file '%s': %s\n",
+                    path, strerror(errno));
+        }
+        else {
+            sts = yy_mql_parse() ? -1 : 0;
+            close(mqlin);
+        }
+    }
+
+    mqlin = -1;
+    
+    return sts;
+}
+
+
+mql_result_t *mql_exec_string(mql_result_type_t result_type, const char *str)
+{
+    MDB_CHECKARG((result_type == mql_result_columns  ||
+                  result_type == mql_result_rows ||
+                  result_type == mql_result_string  ) && 
+                 str, NULL);
+
+    mode = mql_mode_exec;
+    result = NULL;
+    rtype  = result_type;
+    mqlbuf = str;
+
+    if (yy_mql_parse() && !result) {
+        result = mql_result_error_create(EIO, "Syntax error in '%s'", str);
+    }
+
+
+    return result;
+}
+
+mql_statement_t *mql_precompile(const char *str)
+{
+    MDB_CHECKARG(str, NULL);
+
+    mode = mql_mode_precompile;
+    rtype = mql_result_unknown;
+    statement = NULL;
+    mqlbuf = str;
+    
+    yy_mql_parse();
+
+    return statement;
+} 
+
+int yy_mql_input(void *dst, unsigned dstlen)
+{
+    int len = 0;
+
+    if (dst && dstlen > 0) {
+
+        if (mqlbuf) {
+            if ((len = strlen(mqlbuf)) < 1)
+                len = 0;
+            else if (len + 1 <= dstlen) {
+                memcpy(dst, mqlbuf, len + 1);
+                mqlbuf += len;
+            }
+            else {
+                memcpy(dst, mqlbuf, dstlen);
+                mqlbuf += dstlen;
+            }
+        }
+        else if (mqlin >= 0) {
+            while ((len = read(mqlin, dst, dstlen)) < 0) {
+                if (errno != EINTR) {
+                    break;
+                }
+            }
+        }
+    }
+
+    return len;
+}
+
+
+void yy_mql_error(const char *msg)
+{
+    if (mode == mql_mode_parser)
+        fprintf(mqlout, "Error: '%s'\n", msg);
+}
+
+
+
+static void print_query_result(mqi_column_desc_t *coldescs,
+                               mqi_data_type_t   *coltypes,
+                               int               *colsizes,
+                               int                nresult,
+                               int                recsize,
+                               void              *results)
+{
+    int i, j, recoffs;
+    void *data;
+    char  name[4096];
+    int   clgh;
+    int   clghs[MQI_COLUMN_MAX + 1];
+    int   n;
+
+    for (j = 0, n = 0;  j < ncolnam;  j++) {
+        snprintf(name, sizeof(name),  "%s", colnams[j]);
+
+        switch (coltypes[j]) {
+        case mqi_varchar:   clgh = colsizes[j] - 1;  break;
+        case mqi_integer:   clgh = 11;               break;
+        case mqi_unsignd:   clgh = 10;               break;
+        case mqi_floating:  clgh = 10;               break;
+        default:            clgh = 0;                break;
+        }
+
+        clghs[j] = clgh;
+
+        if (clgh < sizeof(name))
+            name[clgh] = '\0';
+
+        n += fprintf(mqlout, "%s%*s", j?" ":"", clgh,name);
+
+    }
+
+    if (n > sizeof(name)-1)
+        n = sizeof(name)-1;
+    memset(name, '-', n);
+    name[n] = '\0';
+
+    fprintf(mqlout, "\n%s\n", name);
+
+
+
+    for (i = 0, recoffs = 0;  i < nresult;  i++, recoffs += recsize) {
+        for (j = 0;  j < ncolnam;  j++) {
+            if (j) fprintf(mqlout, " ");
+
+            data = results + (recoffs + coldescs[j].offset);
+            clgh = clghs[j];
+
+#define PRINT(t,f) fprintf(mqlout, f, clgh, *(t *)data)
+
+            switch (coltypes[j]) {
+            case mqi_varchar:     PRINT(char *  , "%*s"   );    break;
+            case mqi_integer:     PRINT(int32_t , "%*d"   );    break;
+            case mqi_unsignd:     PRINT(uint32_t, "%*u"   );    break;
+            case mqi_floating:    PRINT(double  , "%*.2lf");    break;
+            case mqi_blob:                                      break;
+            default:                                            break;
+            }
+
+#undef PRINT
+
+        }
+        fprintf(mqlout, "\n");
+    }
+}
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ * vim:set expandtab shiftwidth=4:
+ */
diff --git a/src/murphy-db/mql/mql-scanner.l b/src/murphy-db/mql/mql-scanner.l
new file mode 100644 (file)
index 0000000..127fa15
--- /dev/null
@@ -0,0 +1,269 @@
+%{
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "mql-parser.h"
+
+#if 0
+#define DEBUG_SCANNER
+#endif
+
+#ifdef DEBUG_SCANNER
+#define PRINT(fmt, args...) printf(fmt, args) 
+#else
+#define PRINT(fmt, args...)
+#endif
+
+#define YY_SKIP_YYWRAP
+#define YY_NO_INPUT
+
+#define EOF_TOKEN  \
+    YY_FLUSH_BUFFER; \
+    yy_mql_lex_destroy(); \
+    yyterminate()
+
+#define ARGLESS_TOKEN(t) \
+     do { \
+         PRINT("%s-", #t); \
+         yy_mql_lval.string = #t; \
+         return TKN_##t; \
+     } while (0)
+
+#define STRING_TOKEN(t) \
+     do { \
+         PRINT("%s(%s)-", #t, yytext); \
+         yy_mql_lval.string = copy_to_ringbuf(yytext);  \
+         return TKN_##t; \
+     } while (0)
+
+#define NUMBER_TOKEN \
+    do { \
+        yy_mql_lval.number = strtoul(yytext, NULL, 10); \
+        PRINT("NUMBER(%lld)-", yy_mql_lval.number); \
+        return TKN_NUMBER; \
+    } while(0)
+
+#define FLOATING_TOKEN \
+    do { \
+       yy_mql_lval.floating = strtod(yytext, NULL); \
+       return TKN_FLOATING; \
+    } while (0)
+
+#define SEMICOLON_TOKEN \
+     do { \
+         PRINT("%s\n", "SEMICOLON"); \
+         yy_mql_lval.string = "SEMICOLON"; \
+         return TKN_SEMICOLON; \
+     } while (0)
+
+#define PARAMETER_TOKEN \
+    do { \
+        mqi_data_type_t type; \
+        switch(yytext[1]) { \
+        case 's': type = mqi_varchar;  break; \
+        case 'd': type = mqi_integer;  break; \
+        case 'u': type = mqi_unsignd;  break; \
+        case 'f': type = mqi_floating; break; \
+        default : type = mqi_unknown;  break; \
+        } \
+        yy_mql_lval.type = type; \
+        PRINT("PARAMETER(%d)-", yy_mql_lval.type); \
+        return TKN_PARAMETER; \
+    } while(0)
+
+
+#define YY_INPUT(buf, result, max_size) \
+    do { \
+        int n; \
+        if ((n = yy_mql_input(buf, max_size)) >= 0) \
+            result = n; \
+        else { \
+            result = 0; \
+            YY_FATAL_ERROR( "mql input failed" ); \
+        } \
+    } while (0)
+
+
+YYSTYPE  yy_mql_lval;
+
+
+
+static char  ringbuf[4096];
+static char *bufptr = ringbuf;
+static char *bufend = ringbuf + sizeof(ringbuf) - 1;
+
+static char *copy_to_ringbuf(const char *);
+
+
+%}
+
+%option prefix="yy_mql_"
+%option batch
+%option yylineno
+%option case-insensitive
+%option nounput noyymore noyywrap
+
+WHITESPACE        [\ \t\n]+
+
+SHOW              show
+BEGIN             begin
+COMMIT            commit
+ROLLBACK          rollback
+TRANSACTION       transaction
+CREATE            create
+UPDATE            update
+REPLACE           replace
+DELETE            delete
+DROP              drop
+DESCRIBE          describe
+TABLE             table
+TABLES            tables
+INDEX             index
+INSERT            insert
+SELECT            select
+INTO              into
+FROM              from
+WHERE             where
+VALUES            values
+SET               set
+ON                on
+OR                or
+PERSISTENT        persistent
+TEMPORARY         temporary
+
+VARCHAR           varchar
+INTEGER           integer
+UNSIGNED          unsigned
+REAL              real
+BLOB              blob
+PARAMETER         \%[sduf]
+
+LOGICAL_AND       \&
+LOGICAL_OR        \|
+LESS              <
+LESS_OR_EQUAL     <=
+EQUAL             =
+GREATER_OR_EQUAL  >=
+GREATER           >
+NOT               \!
+
+
+NUMBER            [0-9]+
+FLOATING          [0-9]\.[0-9]*
+IDENTIFIER        [a-zA-Z]([a-zA-Z0-9_-]*[a-zA-Z0-9])*
+QUOTED_STRING     (('[^\n'\;]*')|(\"[^\n\"\;]*\"))
+
+LEFT_PAREN        \(
+RIGHT_PAREN       \)
+COMMA             ,
+SEMICOLON         ;
+PLUS              \+
+MINUS             \-
+STAR              \*
+SLASH             \/
+
+
+
+
+%%
+
+<<EOF>>            { EOF_TOKEN;                        }
+{WHITESPACE}       {                                   }
+
+{SHOW}             { ARGLESS_TOKEN (SHOW);             }
+{BEGIN}            { ARGLESS_TOKEN (BEGIN);            }
+{COMMIT}           { ARGLESS_TOKEN (COMMIT);           }
+{ROLLBACK}         { ARGLESS_TOKEN (ROLLBACK);         }
+{TRANSACTION}      { ARGLESS_TOKEN (TRANSACTION);      }
+{CREATE}           { ARGLESS_TOKEN (CREATE);           }
+{UPDATE}           { ARGLESS_TOKEN (UPDATE);           }
+{REPLACE}          { ARGLESS_TOKEN (REPLACE);          }
+{DELETE}           { ARGLESS_TOKEN (DELETE);           }
+{DROP}             { ARGLESS_TOKEN (DROP);             }
+{DESCRIBE}         { ARGLESS_TOKEN (DESCRIBE);         }
+{TABLE}            { ARGLESS_TOKEN (TABLE);            }
+{TABLES}           { ARGLESS_TOKEN (TABLES);           }
+{INDEX}            { ARGLESS_TOKEN (INDEX);            }
+{INSERT}           { ARGLESS_TOKEN (INSERT);           }
+{SELECT}           { ARGLESS_TOKEN (SELECT);           }
+{INTO}             { ARGLESS_TOKEN (INTO);             }
+{FROM}             { ARGLESS_TOKEN (FROM);             }
+{WHERE}            { ARGLESS_TOKEN (WHERE);            }
+{VALUES}           { ARGLESS_TOKEN (VALUES);           }
+{SET}              { ARGLESS_TOKEN (SET);              }
+{ON}               { ARGLESS_TOKEN (ON);               }
+{OR}               { ARGLESS_TOKEN (OR);               }
+{PERSISTENT}       { ARGLESS_TOKEN (PERSISTENT);       }
+{TEMPORARY}        { ARGLESS_TOKEN (TEMPORARY);        }
+
+{VARCHAR}          { ARGLESS_TOKEN (VARCHAR);          }
+{INTEGER}          { ARGLESS_TOKEN (INTEGER);          }
+{UNSIGNED}         { ARGLESS_TOKEN (UNSIGNED);         }
+{REAL}             { ARGLESS_TOKEN (REAL);             }
+{BLOB}             { ARGLESS_TOKEN (BLOB);             }
+{PARAMETER}        { PARAMETER_TOKEN;                  }
+
+{LOGICAL_AND}      { ARGLESS_TOKEN (LOGICAL_AND);      }
+{LOGICAL_OR}       { ARGLESS_TOKEN (LOGICAL_OR);       }
+{LESS}             { ARGLESS_TOKEN (LESS);             }
+{LESS_OR_EQUAL}    { ARGLESS_TOKEN (LESS_OR_EQUAL);    }
+{EQUAL}            { ARGLESS_TOKEN (EQUAL);            }
+{GREATER_OR_EQUAL} { ARGLESS_TOKEN (GREATER_OR_EQUAL); }
+{GREATER}          { ARGLESS_TOKEN (GREATER);          }
+{NOT}              { ARGLESS_TOKEN (NOT);              }
+
+{LEFT_PAREN}       { ARGLESS_TOKEN (LEFT_PAREN);       }
+{RIGHT_PAREN}      { ARGLESS_TOKEN (RIGHT_PAREN);      }
+{COMMA}            { ARGLESS_TOKEN (COMMA);            }
+{SEMICOLON}        { SEMICOLON_TOKEN;                  }
+{PLUS}             { ARGLESS_TOKEN (PLUS);             }
+{MINUS}            { ARGLESS_TOKEN (MINUS);            }
+{STAR}             { ARGLESS_TOKEN (STAR);             }
+{SLASH}            { ARGLESS_TOKEN (SLASH);            }
+
+{NUMBER}           { NUMBER_TOKEN;                     }
+{FLOATING}         { FLOATING_TOKEN;                   }
+{IDENTIFIER}       { STRING_TOKEN (IDENTIFIER);        }
+{QUOTED_STRING}    { STRING_TOKEN (QUOTED_STRING);     }
+
+%%
+
+
+
+static char *copy_to_ringbuf(const char *string)
+{
+   const char *src;
+   char       *copy;
+   char        qt;
+
+   for (;;) {
+        if (bufptr >= bufend)
+           bufptr = ringbuf;
+
+        copy = bufptr;
+
+        switch (*(src = string)) {
+        case '\'':  qt = *src++;   break;
+        case '\"':  qt = *src++;   break;
+        default:    qt =  0xff;    break;
+        }
+
+        while (bufptr < bufend) {
+            if (!(*bufptr++ = *src++)) {
+                if (bufptr[-2] == qt)
+                  (--bufptr)[-1] = '\0';
+                return copy;
+            }
+        }
+   }
+}
+
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ * vim:set expandtab shiftwidth=4:
+ */
diff --git a/src/murphy-db/mql/result.c b/src/murphy-db/mql/result.c
new file mode 100644 (file)
index 0000000..1d2757e
--- /dev/null
@@ -0,0 +1,923 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <alloca.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include <murphy-db/assert.h>
+#include <murphy-db/result.h>
+#include "mql-parser.h"
+
+typedef struct {
+    int                   cindex;
+    mqi_data_type_t       type;
+    int                   offset;
+} column_desc_t;
+
+typedef struct {
+    int                   code;
+    char                  msg[0];
+} error_desc_t;
+
+typedef struct {
+    mql_result_type_t     type;
+    error_desc_t          error;
+} result_error_t;
+
+typedef struct {
+    mql_result_type_t     type;
+    int                   ncol;
+    mqi_column_def_t      cols[0];
+} result_columns_t;
+
+typedef struct {
+    mql_result_type_t     type;
+    int                   rowsize;
+    int                   ncol;
+    int                   nrow;
+    void                 *data;
+    column_desc_t         cols[0];
+} result_rows_t;
+
+typedef struct {
+    mql_result_type_t     type;
+    char                  string[0];
+} result_string_t;
+
+typedef struct {
+    mql_result_type_t     type;
+    int                   length;
+    struct {
+        mqi_data_type_t   type;
+        union {
+            char     *varchar[0];
+            int32_t   integer[0];
+            uint32_t  unsignd[0];
+            double    floating[0];
+            int       generic[0];
+        };
+    }                     value;
+} result_list_t;
+
+static inline mqi_data_type_t get_column_type(result_rows_t *, int);
+static inline void *get_column_address(result_rows_t *, int, int);
+
+
+int mql_result_is_success(mql_result_t *r)
+{
+    result_error_t *e = (result_error_t *)r;
+
+    if (e) {
+        if (e->type != mql_result_error)
+            return 1;
+
+        if (!e->error.code)
+            return 1;
+    }
+
+    return 0;
+}
+
+mql_result_t *mql_result_success_create(void)
+{
+    return mql_result_error_create(0, "Success");
+}
+
+mql_result_t *mql_result_error_create(int code, const char *fmt, ...)
+{
+    va_list ap;
+    result_error_t *rslt;
+    char buf[1024];
+    int l;
+    
+    MDB_CHECKARG(code >= 0 && fmt, NULL);
+
+    va_start(ap, fmt);
+    l = vsnprintf(buf, sizeof(buf), fmt, ap);
+    va_end(ap);
+
+    if (l > sizeof(buf))
+        l = sizeof(buf) - 1;
+
+    if ((rslt = calloc(1, sizeof(result_error_t) + l + 1))) {
+        rslt->type = mql_result_error;
+        rslt->error.code = code;
+        memcpy(rslt->error.msg, buf, l);
+    }
+
+    return (mql_result_t *)rslt;
+}
+
+int mql_result_error_get_code(mql_result_t *r)
+{
+    int code;
+
+    MDB_CHECKARG(r, -1);
+
+    if (r->type != mql_result_error)
+        code = 0;
+    else {
+        result_error_t *rslt = (result_error_t *)r;
+        code = rslt->error.code;
+    }
+
+    return code;
+}
+
+const char *mql_result_error_get_message(mql_result_t *r)
+{
+    const char *msg;
+
+    MDB_CHECKARG(r, NULL);
+
+    if (r->type != mql_result_error)
+        msg = "Success";
+    else {
+        result_error_t *rslt = (result_error_t *)r;
+        msg = rslt->error.msg;
+    }
+
+    return msg;
+}
+
+mql_result_t *mql_result_columns_create(int ncol, mqi_column_def_t *defs)
+{
+    result_columns_t *rslt;
+    mqi_column_def_t *col;
+    size_t            poollen;
+    size_t            dlgh;
+    int               namlen[MQI_COLUMN_MAX];
+    char             *strpool;
+    int               i;
+
+    MDB_CHECKARG(ncol > 0 && ncol < MQI_COLUMN_MAX && defs, NULL);
+
+    for (poollen = 0, i = 0;  i < ncol;  i++)
+        poollen += (namlen[i] = strlen(defs[i].name) + 1);
+
+    dlgh = sizeof(mqi_column_def_t) * ncol;
+
+    if (!(rslt = calloc(1, sizeof(result_columns_t) + dlgh + poollen))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    rslt->type = mql_result_columns;
+    rslt->ncol = ncol;
+
+    memcpy(rslt->cols, defs, dlgh);
+
+    strpool = (char *)(rslt->cols + ncol);
+
+    for (i = 0;    i < ncol;    i++) {
+        col = rslt->cols + i;
+        col->name = memcpy(strpool, col->name, namlen[i]);
+        strpool += namlen[i];
+    }
+
+    return (mql_result_t *)rslt;
+}
+
+int mql_result_columns_get_column_count(mql_result_t *r)
+{
+    result_columns_t *rslt = (result_columns_t *)r;
+
+    MDB_CHECKARG(rslt && rslt->type == mql_result_columns, -1);
+
+    return rslt->ncol;
+}
+
+const char *mql_result_columns_get_name(mql_result_t *r, int colidx)
+{
+    result_columns_t *rslt = (result_columns_t *)r;
+
+    MDB_CHECKARG(rslt && rslt->type == mql_result_columns &&
+                 colidx >= 0 && colidx < rslt->ncol, NULL);
+
+    return rslt->cols[colidx].name;
+}
+
+mqi_data_type_t mql_result_columns_get_type(mql_result_t *r, int colidx)
+{
+    result_columns_t *rslt = (result_columns_t *)r;
+
+    MDB_CHECKARG(rslt && rslt->type == mql_result_columns &&
+                 colidx >= 0 && colidx < rslt->ncol, -1);
+
+    return rslt->cols[colidx].type;
+}
+
+int mql_result_columns_get_length(mql_result_t *r, int colidx)
+{
+    result_columns_t *rslt = (result_columns_t *)r;
+
+    MDB_CHECKARG(rslt && rslt->type == mql_result_columns &&
+                 colidx >= 0 && colidx < rslt->ncol, -1);
+
+    return rslt->cols[colidx].length;
+}
+
+
+uint32_t mql_result_columns_get_flags(mql_result_t *r, int colidx)
+{
+    result_columns_t *rslt = (result_columns_t *)r;
+
+    MDB_CHECKARG(rslt && rslt->type == mql_result_columns &&
+                 colidx >= 0 && colidx < rslt->ncol, -1);
+
+    return rslt->cols[colidx].flags;
+}
+
+
+mql_result_t *mql_result_rows_create(int                ncol,
+                                     mqi_column_desc_t *coldescs,
+                                     mqi_data_type_t   *coltypes,
+                                     int               *colsizes,
+                                     int                nrow,
+                                     int                rowsize,
+                                     void              *rows)
+
+{
+    result_rows_t     *rslt;
+    column_desc_t     *col;
+    mqi_column_desc_t *cd;
+    int                offs;
+    size_t             size;
+    size_t             dlgh;
+    int                i;
+
+    MDB_CHECKARG(ncol >  0 && coldescs && coltypes && colsizes &&
+                 nrow >= 0 && rowsize > 0 && rows, NULL);
+
+    offs = sizeof(column_desc_t) * ncol;
+    dlgh = rowsize * nrow;
+    size = sizeof(result_rows_t) + offs + dlgh;
+
+
+    if (!(rslt = calloc(1, size))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    rslt->type    = mql_result_rows;
+    rslt->rowsize = rowsize;
+    rslt->ncol    = ncol;
+    rslt->nrow    = nrow;
+    rslt->data    = rslt->cols + ncol;
+
+    for (i = 0;   i < ncol;  i++) {
+        col = rslt->cols + i;
+        cd  = coldescs + i;
+
+        col->cindex = cd->cindex;
+        col->type   = coltypes[i];
+        col->offset = cd->offset;
+    }
+
+    if (dlgh > 0)
+        memcpy(rslt->data, rows, dlgh);
+
+    return (mql_result_t *)rslt;
+}
+
+int mql_result_rows_get_row_count(mql_result_t *r)
+{
+    result_rows_t *rslt = (result_rows_t *)r;
+
+    MDB_CHECKARG(rslt && rslt->type == mql_result_rows, -1);
+
+    return rslt->nrow;
+}
+
+const char *mql_result_rows_get_string(mql_result_t *r, int colidx, int rowidx,
+                                       char *buf, int len)
+{
+    result_rows_t *rslt = (result_rows_t *)r;
+    void *addr;
+    char *v = NULL;
+
+    MDB_CHECKARG(rslt && rslt->type == mql_result_rows &&
+                 colidx >= 0 && colidx < rslt->ncol &&
+                 rowidx >= 0 && rowidx < rslt->nrow &&
+                 (!buf || (buf && len > 0)), NULL);
+
+    if ((v = buf))
+        *v = '\0';
+
+    if ((addr = get_column_address(rslt, colidx, rowidx))) {
+        switch (get_column_type(rslt, colidx)) {
+        case mqi_varchar:
+            if (!v)
+                v = *(char **)addr;
+            else {
+                strncpy(v, *(char **)addr, len);
+                v[len-1] = '\0';
+            }
+            break;
+            
+        case mqi_integer:
+            if (!v)
+                v = "";
+            else
+                snprintf(v, len, "%d", *(int32_t *)addr);
+            break;
+            
+        case mqi_unsignd:
+            if (!v)
+                v = "";
+            else
+                snprintf(v, len, "%u", *(uint32_t *)addr);
+            break;
+            
+        case mqi_floating:
+            if (!v)
+                v = "";
+            else
+                snprintf(v, len, "%lf", *(double *)addr);
+            break;
+            
+        default:
+            v = "";
+            break;
+        }
+    }
+
+    return v;
+}
+
+int32_t mql_result_rows_get_integer(mql_result_t *r, int colidx, int rowidx)
+{
+    result_rows_t *rslt = (result_rows_t *)r;
+    void *addr;
+    int32_t v = 0;
+
+    MDB_CHECKARG(rslt && rslt->type == mql_result_rows &&
+                 colidx >= 0 && colidx < rslt->ncol &&
+                 rowidx >= 0 && rowidx < rslt->nrow, 0);
+
+    if ((addr = get_column_address(rslt, colidx, rowidx))) {
+        switch (get_column_type(rslt, colidx)) {
+        case mqi_varchar:    v = strtol(*(char **)addr, NULL, 10);   break;
+        case mqi_integer:    v = *(int32_t *)addr;                   break;
+        case mqi_unsignd:    v = *(uint32_t *)addr;                  break;
+        case mqi_floating:   v = *(double *)addr;                    break;
+        default:             v = 0;                                  break;
+        }
+    }
+
+    return v;
+}
+
+uint32_t mql_result_rows_get_unsigned(mql_result_t *r, int colidx, int rowidx)
+{
+    result_rows_t *rslt = (result_rows_t *)r;
+    void *addr;
+    uint32_t v = 0;
+
+    MDB_CHECKARG(rslt && rslt->type == mql_result_rows &&
+                 colidx >= 0 && colidx < rslt->ncol &&
+                 rowidx >= 0 && rowidx < rslt->nrow, 0);
+
+    if ((addr = get_column_address(rslt, colidx, rowidx))) {
+        switch (get_column_type(rslt, colidx)) {
+        case mqi_varchar:    v = strtoul(*(char **)addr, NULL, 10);  break;
+        case mqi_integer:    v = *(int32_t *)addr;                   break;
+        case mqi_unsignd:    v = *(uint32_t *)addr;                  break;
+        case mqi_floating:   v = *(double *)addr;                    break;
+        default:             v = 0;                                  break;
+        }
+    }
+
+    return v;
+}
+
+double mql_result_rows_get_floating(mql_result_t *r, int colidx, int rowidx)
+{
+    result_rows_t *rslt = (result_rows_t *)r;
+    void *addr;
+    double v = 0;
+
+    MDB_CHECKARG(rslt && rslt->type == mql_result_rows &&
+                 colidx >= 0 && colidx < rslt->ncol &&
+                 rowidx >= 0 && rowidx < rslt->nrow, 0.0);
+
+    if ((addr = get_column_address(rslt, colidx, rowidx))) {
+        switch (get_column_type(rslt, colidx)) {
+        case mqi_varchar:    v = strtod(*(char **)addr, NULL);    break;
+        case mqi_integer:    v = *(int32_t *)addr;                break;
+        case mqi_unsignd:    v = *(uint32_t *)addr;               break;
+        case mqi_floating:   v = *(double *)addr;                 break;
+        default:             v = 0;                               break;
+        }
+    }
+
+    return v;
+}
+
+
+mql_result_t *mql_result_string_create_table_list(int n, char **names)
+{
+    static const char *no_tables = "no tables\n";
+    result_string_t *rslt;
+    int    nlgh[4096];
+    char  *name;
+    char   first_letter, upper;
+    int    len;
+    char  *p;
+    int    i;
+
+    MDB_CHECKARG(n >= 0 && n < MQI_DIMENSION(nlgh) && names, NULL);
+
+    len = sizeof(result_string_t) + 1;
+
+    if (!n)
+        len += strlen(no_tables) + 1;
+    else {
+        for (i = 0, first_letter = 0;    i < n;    i++) {
+            name  = names[i];
+            upper = toupper(name[0]);
+            
+            if (upper != first_letter) {
+                first_letter = upper;
+                len += 3; /* \n[A-Z]: */
+            }
+
+            len += (nlgh[i] = strlen(name)) + 1;
+        }
+    }
+        
+    if (!(rslt = calloc(1, len))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    rslt->type = mql_result_string;
+
+    if (!n)
+        strcpy(rslt->string, no_tables);
+    else {
+        for (p = rslt->string, first_letter = 0, i = 0;     i < n;     i++) {
+            name  = names[i];
+            len   = nlgh[i];
+            upper = toupper(name[0]);
+            
+            if (upper != first_letter) {
+                if (first_letter)
+                    *p++ = '\n';
+                
+                first_letter = upper;
+                
+                *p++ = upper;
+                *p++ = ':';
+            }
+            
+            *p++ = ' ';
+            
+            memcpy(p, name, len);
+            p += len;
+        }
+
+        *p++ = '\n';
+    }
+    
+    return (mql_result_t *)rslt;
+}
+
+
+mql_result_t *mql_result_string_create_column_list(int               ncol,
+                                                   mqi_column_def_t *defs)
+{
+#define INDEX   0
+#define NAME    1
+#define TYPE    2
+#define LENGTH  3
+#define FLDS    4
+
+    static const char *hstr[FLDS]  = {"index", " name" , "type", "length"};
+    static int         hlen[FLDS]  = {   5   ,     5   ,    4  ,     6   };
+    static int         align[FLDS] = {  +1   ,    -1   ,   -1  ,    +1   };
+
+    result_string_t  *rslt;
+    mqi_column_def_t *def;
+    const char       *typstr[MQI_COLUMN_MAX];
+    int               namlen[MQI_COLUMN_MAX];
+    int               typlen[MQI_COLUMN_MAX];
+    int               fldlen[FLDS];
+    int               linlen;
+    int               len;
+    int               offs;
+    char             *p, *q;
+    int               i, j;
+
+    MDB_CHECKARG(ncol > 0 && ncol < MQI_COLUMN_MAX && defs, NULL);
+
+    memcpy(fldlen, hlen, sizeof(fldlen));
+
+    for (i = 0;  i < ncol;  i++) {
+        def = defs + i;
+
+        typstr[i] = mqi_data_type_str(def->type);
+
+        if ((len = (namlen[i] = strlen(def->name)) + 1) > fldlen[NAME])
+            fldlen[NAME] = len;
+
+        if ((len = (typlen[i] = strlen(typstr[i]))) > fldlen[TYPE])
+            fldlen[TYPE] = len;
+    }
+
+    for (linlen = FLDS, i = 0;  i < FLDS;  linlen += fldlen[i++])
+        ;
+
+
+    /*
+     * allocate and initialize the result structure
+     */
+    if (!(rslt = calloc(1, sizeof(result_string_t) + linlen * (ncol+2) + 1))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    rslt->type = mql_result_string;
+    p = rslt->string;
+    memset(p, ' ', linlen * (ncol+2));
+
+    /*
+     * labels
+     */
+    for (q = p, j = 0;   j < FLDS;   q += (fldlen[j++] + 1)) {
+        offs = (align[j] < 0) ? 0 : fldlen[j] - hlen[j]; 
+        memcpy(q + offs, hstr[j], hlen[j]);
+    }
+
+    p += linlen;
+    *(p-1) = '\n';
+
+    /*
+     * separator line
+     */
+    memset(p, '-', linlen);
+    p += linlen;
+    *(p-1) = '\n';
+    
+        
+    /*
+     * data lines
+     */
+    for (i = 0;  i < ncol;  i++, p += linlen) {
+        def = defs + i;
+        snprintf(p, linlen+1, "%*d %s%-*s %-*s %*d\n",
+                 fldlen[INDEX], i,
+                 def->flags&MQI_COLUMN_KEY ? "*":" ", fldlen[NAME]-1,def->name,
+                 fldlen[TYPE], typstr[i],
+                 fldlen[LENGTH], def->length);
+    }
+    
+    return (mql_result_t *)rslt;
+
+#undef FLDS
+#undef LENGTH
+#undef TYPE
+#undef NAME
+#undef INDEX
+}
+
+mql_result_t *mql_result_string_create_row_list(int                 ncol,
+                                                char              **colnams,
+                                                mqi_column_desc_t  *coldescs,
+                                                mqi_data_type_t    *coltypes,
+                                                int                *colsizes,
+                                                int                 nrow,
+                                                int                 rowsize,
+                                                void               *rows)
+{
+    static const char *no_rows = "no rows\n";
+
+    result_string_t *rslt;
+    size_t  len;
+    int     rwidth;
+    int     cwidth;
+    int     cwidths[MQI_COLUMN_MAX + 1];
+    void   *row;
+    void   *column;
+    int     i,j;
+    char   *p;
+
+
+    MDB_CHECKARG(ncol >  0 && coldescs && coltypes && colsizes &&
+                 nrow >= 0 && rowsize > 0 && rows, NULL);
+
+    /*
+     * calculate column widths and row width
+     */
+    rwidth = ncol;  /* for each column a separating space or a \n at the end */
+
+    for (i = 0;   i < ncol;   i++) {
+        switch (coltypes[i]) {
+        case mqi_varchar:   cwidth = colsizes[i] - 1;  break;
+        case mqi_integer:   cwidth = 11;               break;
+        case mqi_unsignd:   cwidth = 10;               break;
+        case mqi_floating:  cwidth = 10;               break;
+        default:            cwidth = 0;                break;
+        }
+
+        rwidth += (cwidths[i] = cwidth);
+    }
+
+    if (!nrow)
+        len = sizeof(result_string_t) + rwidth * 2 + strlen(no_rows) + 1;
+    else
+        len = sizeof(result_string_t) + rwidth * (nrow + 2) + 1;
+
+    /*
+     * setup the result structure
+     */
+    if (!(rslt = calloc(1, len))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    rslt->type = mql_result_string;
+
+    p = rslt->string;
+
+    /*
+     * labels
+     */
+    for (i = 0;   i < ncol;   i++) {
+        if ((cwidth = cwidths[i])) {
+            if (cwidth <= (len = strlen(colnams[i]))) {
+                /* truncate */
+                memcpy(p, colnams[i], cwidth);
+                p[cwidth] = ' ';
+            }
+            else {
+                if (coltypes[i] == mqi_varchar) {
+                    /* left align */
+                    memcpy(p, colnams[i], len);
+                    memset(p + len, ' ', (cwidth + 1) - len);
+                }
+                else {
+                    /* right align */
+                    memset(p, ' ', cwidth - len);
+                    memcpy(p + (cwidth - len), colnams[i], len);
+                    p[cwidth] = ' ';
+                }
+            }
+            p += cwidth + 1;
+        }
+    } /* for */
+
+    *(p-1) = '\n';
+
+    /*
+     * separator line
+     */
+    memset(p, '-', rwidth-1);
+    p[rwidth-1] = '\n';
+    p += rwidth;
+    
+    /*
+     * data lines
+     */
+#define SPRINT(t,f) snprintf(p, cwidth+2, f " ", cwidth, *(t *)column)
+
+    if (!nrow)
+        strcpy(p, no_rows);
+    else {
+        for (i = 0, row = rows;  i < nrow;  i++, row += rowsize) {
+            for (j = 0;  j < ncol; j++) {
+                column = row + coldescs[j].offset;
+                cwidth = cwidths[j];
+                
+                switch (coltypes[j]) {
+                case mqi_varchar:     SPRINT( char *  , "%-*s"   );     break;
+                case mqi_integer:     SPRINT( int32_t , "%*d"    );     break;
+                case mqi_unsignd:     SPRINT( uint32_t, "%*u"    );     break;
+                case mqi_floating:    SPRINT( double  , "%*.2lf" );     break;
+                default:              memset(p, ' ', cwidth+1    );     break;
+                }
+                
+                p += cwidth+1;
+            }
+            
+            *(p-1) = '\n';
+        }
+        *p = '\0';
+    }
+
+#undef SPRINT
+
+    return (mql_result_t *)rslt;
+}
+
+const char *mql_result_string_get(mql_result_t *r)
+{
+    result_string_t *rslt = (result_string_t *)r;
+
+    MDB_CHECKARG(rslt && rslt->type == mql_result_string, "");
+
+    return rslt->string;
+}
+
+
+mql_result_t *mql_result_list_create(mqi_data_type_t type,
+                                     int             length,
+                                     void           *values)
+{
+    result_list_t *rslt;
+    size_t   datalen;
+    char   **strs;
+    int     *slen;
+    char    *strpool;
+    int      poollen;
+    int      i;
+
+    MDB_CHECKARG(length > 0 && values, NULL);
+
+    switch (type) {
+    case mqi_varchar:
+        slen = alloca(sizeof(int) * length);
+        for (strs = (char **)values, i = 0;  i < length;  i++)
+            poollen += (slen[i] = strlen(strs[i]) + 1);
+        datalen = sizeof(char *) * length + poollen;
+        break;        
+    case mqi_integer:
+        poollen = 0;
+        datalen = sizeof(int32_t) * length;
+        break;
+    case mqi_unsignd:
+        poollen = 0;
+        datalen = sizeof(uint32_t) * length;
+        break;
+    case mqi_floating:
+        poollen = 0;
+        datalen = sizeof(double) * length;
+        break;
+    default:
+        errno = EINVAL;
+        return NULL;
+    }
+
+    if ((rslt = calloc(1, sizeof(result_list_t) + datalen))) {
+        rslt->type   = mql_result_list;
+        rslt->length = length;
+        rslt->value.type = type;
+
+        if (type != mqi_varchar)
+            memcpy(rslt->value.generic, values, datalen);
+        else {
+            strs = (char **)values;
+            strpool = (char *)&rslt->value.varchar[length];
+            for (i = 0;  i < length;  i++) {
+                rslt->value.varchar[i] = memcpy(strpool, strs[i], slen[i]);
+                strpool += slen[i];
+            }
+        }
+    }
+
+    return (mql_result_t *)rslt;
+}
+
+int mql_result_list_get_length(mql_result_t *r)
+{
+    result_list_t *rslt = (result_list_t *)r;
+
+    MDB_CHECKARG(rslt && rslt->type == mql_result_list, -1);
+
+    return rslt->length;
+}
+
+const char *mql_result_list_get_string(mql_result_t *r, int idx,
+                                       char *buf, int len)
+{
+    result_list_t *rslt = (result_list_t *)r;
+    char *v = NULL;
+
+    MDB_CHECKARG(rslt && rslt->type == mql_result_list &&
+                 idx >= 0 && idx < rslt->length, NULL);
+
+    if ((v = buf))
+        *v = '\0';
+
+    switch (rslt->value.type) {
+
+    case mqi_varchar:
+        if (!v)
+            v = rslt->value.varchar[idx];
+        else {
+            strncpy(v, rslt->value.varchar[idx], len);
+            v[len-1] = '\0';
+        }
+        break;
+
+    case mqi_integer:
+        if (!v)
+            v = "";
+        else
+            snprintf(v, len, "%d", rslt->value.integer[idx]);
+        break;
+
+    case mqi_unsignd:
+        if (!v)
+            v = "";
+        else
+            snprintf(v, len, "%u", rslt->value.unsignd[idx]);
+        break;
+
+    case mqi_floating:
+        if (!v)
+            v = "";
+        else
+            snprintf(v, len, "%lf", rslt->value.floating[idx]);
+        break;
+
+    default:
+        v = "";
+        break;
+    }
+
+    return v;
+}
+
+int32_t mql_result_list_get_integer(mql_result_t *r, int idx)
+{
+    result_list_t *rslt = (result_list_t *)r;
+    int32_t v = 0;
+
+    MDB_CHECKARG(rslt && rslt->type == mql_result_list &&
+                 idx >= 0 && idx < rslt->length, 0);
+
+    switch (rslt->value.type) {
+    case mqi_varchar:   v = strtol(rslt->value.varchar[idx], NULL, 10);  break;
+    case mqi_integer:   v = rslt->value.integer[idx];                    break;
+    case mqi_unsignd:   v = rslt->value.unsignd[idx];                    break;
+    case mqi_floating:  v = rslt->value.floating[idx];                   break;
+    default:            v = 0;                                           break;
+    }
+
+    return v;
+}
+
+int32_t mql_result_list_get_unsigned(mql_result_t *r, int idx)
+{
+    result_list_t *rslt = (result_list_t *)r;
+    uint32_t v = 0;
+
+    MDB_CHECKARG(rslt && rslt->type == mql_result_list &&
+                 idx >= 0 && idx < rslt->length, 0);
+
+    switch (rslt->value.type) {
+    case mqi_varchar:   v = strtoul(rslt->value.varchar[idx], NULL, 10); break;
+    case mqi_integer:   v = rslt->value.integer[idx];                    break;
+    case mqi_unsignd:   v = rslt->value.unsignd[idx];                    break;
+    case mqi_floating:  v = rslt->value.floating[idx];                   break;
+    default:            v = 0;                                           break;
+    }
+
+    return v;
+}
+
+double mql_result_list_get_floating(mql_result_t *r, int idx)
+{
+    result_list_t *rslt = (result_list_t *)r;
+    double v = 0;
+
+    MDB_CHECKARG(rslt && rslt->type == mql_result_list &&
+                 idx >= 0 && idx < rslt->length, 0);
+
+    switch (rslt->value.type) {
+    case mqi_varchar:   v = strtod(rslt->value.varchar[idx], NULL);  break;
+    case mqi_integer:   v = rslt->value.integer[idx];                break;
+    case mqi_unsignd:   v = rslt->value.unsignd[idx];                break;
+    case mqi_floating:  v = rslt->value.floating[idx];               break;
+    default:            v = 0;                                       break;
+    }
+
+    return v;
+}
+
+void mql_result_free(mql_result_t *r)
+{
+    free(r);
+}
+
+
+static mqi_data_type_t get_column_type(result_rows_t *rslt, int cx)
+{
+    return rslt->cols[cx].type;
+}
+
+static void *get_column_address(result_rows_t *rslt, int cx, int rx)
+{
+    return rslt->data + (rslt->rowsize * rx + rslt->cols[cx].offset);
+}
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mql/statement.c b/src/murphy-db/mql/statement.c
new file mode 100644 (file)
index 0000000..e32cdd4
--- /dev/null
@@ -0,0 +1,907 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <alloca.h>
+#include <errno.h>
+
+#include <murphy-db/assert.h>
+#include <murphy-db/mql.h>
+#include "mql-parser.h"
+
+typedef struct {
+    mqi_data_type_t  type;
+    union {
+        const char  *varchar;
+        int32_t      integer;
+        uint32_t     unsignd;
+        double       floating;
+        void        *generic;
+    };
+} value_t;
+
+typedef struct {
+    mql_statement_type_t type;
+    uint32_t             flags;
+} shtable_statement_t;
+
+typedef struct {
+    mql_statement_type_t type;
+    mqi_handle_t         table;
+} describe_statement_t;
+
+typedef struct {
+    mql_statement_type_t type;
+    mqi_handle_t         table;
+    int                  ignore;
+    int                  ncolumn;
+    mqi_column_desc_t   *columns;
+    void                *rows[2];
+    int                  nbind;
+    value_t              values[0];
+} insert_statement_t;
+
+typedef struct {
+    mql_statement_type_t type;
+    mqi_handle_t         table;
+    mqi_column_desc_t   *columns;
+    mqi_cond_entry_t    *cond;
+    int                  nbind;
+    value_t              values[0];
+} update_statement_t;
+
+typedef struct {
+    mql_statement_type_t  type;
+    mqi_handle_t          table;
+    mqi_cond_entry_t     *cond;
+    int                   nbind;
+    value_t               values[0];
+} delete_statement_t;
+
+typedef struct {
+    mql_statement_type_t type;
+    mqi_handle_t         table;
+    int                  rowsize;
+    int                  ncolumn;
+    mqi_column_desc_t   *columns;
+    char               **colnames;
+    mqi_data_type_t     *coltypes;
+    int                 *colsizes;
+    mqi_cond_entry_t    *cond;
+    int                  nbind;
+    value_t              values[0];
+} select_statement_t;
+
+static void count_condition_values(int, mqi_cond_entry_t *, int*, int*, int*);
+static void count_column_values(mqi_column_desc_t *, mqi_data_type_t*, void*,
+                                int *, int *, int *);
+static void copy_column_values(int, mqi_data_type_t *, mqi_column_desc_t *,
+                               mqi_column_desc_t *, value_t **, value_t **,
+                               char **, void *, void *);
+static void copy_conditions_and_values(int,mqi_cond_entry_t*,mqi_cond_entry_t*,
+                                       value_t**, value_t**, char**);
+
+static mql_result_t *exec_show_tables(mql_result_type_t, shtable_statement_t*);
+static mql_result_t *exec_describe(mql_result_type_t, describe_statement_t *);
+static mql_result_t *exec_insert(insert_statement_t *);
+static mql_result_t *exec_update(update_statement_t *);
+static mql_result_t *exec_delete(delete_statement_t *);
+static mql_result_t *exec_select(mql_result_type_t, select_statement_t *);
+
+static int bind_update_value(update_statement_t *,int,mqi_data_type_t,va_list);
+static int bind_delete_value(delete_statement_t *,int,mqi_data_type_t,va_list);
+static int bind_select_value(select_statement_t *,int,mqi_data_type_t,va_list);
+static int bind_value(value_t *, mqi_data_type_t, va_list);
+
+mql_statement_t *mql_make_show_tables_statement(uint32_t flags)
+{
+    shtable_statement_t *st;
+
+    if (!(st = calloc(1, sizeof(shtable_statement_t)))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    st->type  = mql_statement_show_tables;
+    st->flags = flags;
+
+    return (mql_statement_t *)st;
+}
+
+mql_statement_t *mql_make_describe_statement(mqi_handle_t table)
+{
+    describe_statement_t *dis;
+
+    MDB_CHECKARG(table != MQI_HANDLE_INVALID, NULL);
+
+    if (!(dis = calloc(1, sizeof(describe_statement_t)))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    dis->type  = mql_statement_describe;
+    dis->table = table;
+
+    return (mql_statement_t *)dis;
+}
+
+
+mql_statement_t *mql_make_insert_statement(mqi_handle_t       table,
+                                           int                ignore,
+                                           int                ncolumn,
+                                           mqi_data_type_t   *coltypes,
+                                           mqi_column_desc_t *columns,
+                                           void              *data)
+{
+    insert_statement_t *ins;
+    value_t *bindv;
+    value_t *constv;
+    char    *strpool;
+    int      vallgh;
+    int      cdsclgh;
+    int      datalgh;
+    int      nbind   = 0;
+    int      nconst  = 0;
+    int      poollen = 0;
+
+    MDB_CHECKARG(table != MQI_HANDLE_INVALID &&
+                 ncolumn > 0 && columns && data, NULL);
+
+    /*
+     * calculate the number of constant and bindable values
+     */
+    count_column_values(columns, coltypes, data, &nbind,&nconst,&poollen);
+
+    /*
+     * set up the statement structure
+     */
+    vallgh  = sizeof(     value_t     ) * (nbind + nconst);
+    cdsclgh = sizeof(mqi_column_desc_t) * (ncolumn + 1);
+
+    datalgh = vallgh + cdsclgh + poollen;
+
+    if (!(ins = calloc(1, sizeof(insert_statement_t) + datalgh))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    ins->type    = mql_statement_insert;
+    ins->table   = table;
+    ins->ignore  = ignore;
+    ins->columns = (mqi_column_desc_t *)(ins->values + (nbind + nconst));
+    ins->rows[0] = ins->values;
+    ins->nbind   = nbind;
+
+    strpool = (void *)(ins->columns + (ncolumn + 1));
+
+    /*
+     * copy column values
+     */
+    bindv  = ins->values;
+    constv = bindv + nbind;
+
+    copy_column_values(ncolumn, coltypes, columns, ins->columns,
+                       &bindv, &constv, &strpool, data, ins->values);
+
+    return (mql_statement_t *)ins;
+}
+
+
+mql_statement_t *mql_make_update_statement(mqi_handle_t       table,
+                                           int                ncond,
+                                           mqi_cond_entry_t  *conds,
+                                           int                ncolumn,
+                                           mqi_data_type_t   *coltypes,
+                                           mqi_column_desc_t *columns,
+                                           void              *data)
+{
+    update_statement_t *upd;
+    value_t *bindv;
+    value_t *constv;
+    char    *strpool;
+    int      vallgh;
+    int      cdsclgh;
+    int      cndlgh;
+    int      datalgh;
+    int      nbind   = 0;
+    int      nconst  = 0;
+    int      poollen = 0;
+
+    MDB_CHECKARG(table != MQI_HANDLE_INVALID &&
+                 (!ncond || (ncond > 0 && conds)) &&
+                 ncolumn > 0 && coltypes && columns && data, NULL);
+
+    /*
+     * calculate the number of constant and bindable values
+     */
+    count_column_values(columns, coltypes, data, &nbind,&nconst,&poollen);
+    count_condition_values(ncond, conds, &nbind, &nconst, &poollen);
+
+    /*
+     * set up the statement structure
+     */
+    vallgh  = sizeof(     value_t     ) * (nbind + nconst);
+    cdsclgh = sizeof(mqi_column_desc_t) * (ncolumn + 1);
+    cndlgh  = sizeof(mqi_cond_entry_t ) *  ncond;
+
+    datalgh = vallgh + cdsclgh + cndlgh + poollen;
+
+    if (!(upd = calloc(1, sizeof(update_statement_t) + datalgh))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    upd->type    = mql_statement_update;
+    upd->table   = table;
+    upd->columns = (mqi_column_desc_t *)(upd->values + (nbind + nconst));
+    upd->cond    = (mqi_cond_entry_t *)(upd->columns + (ncolumn + 1));
+    upd->nbind   = nbind;
+
+    strpool = (void *)(upd->cond + ncond);
+
+    /*
+     * copy column values, conditions and their values
+     */
+    bindv  = upd->values;
+    constv = bindv + nbind;
+
+    copy_column_values(ncolumn, coltypes, columns, upd->columns,
+                       &bindv, &constv, &strpool, data, upd->values);
+
+    copy_conditions_and_values(ncond, conds, upd->cond,
+                               &bindv, &constv, &strpool);
+
+    return (mql_statement_t *)upd;
+}
+
+
+mql_statement_t *mql_make_delete_statement(mqi_handle_t       table,
+                                           int                ncond,
+                                           mqi_cond_entry_t  *conds)
+{
+    delete_statement_t *del;
+    value_t *bindv;
+    value_t *constv;
+    char    *strpool;
+    int      vallgh;
+    int      cndlgh;
+    int      datalgh;
+    int      nbind   = 0;
+    int      nconst  = 0;
+    int      poollen = 0;
+
+    MDB_CHECKARG(table != MQI_HANDLE_INVALID &&
+                 (!ncond || (ncond > 0 && conds)), NULL);
+
+    /*
+     * calculate the number of constant and bindable values
+     */
+    count_condition_values(ncond, conds, &nbind, &nconst, &poollen);
+
+    /*
+     * set up the statement structure
+     */
+    vallgh  = sizeof(     value_t     ) * (nbind + nconst);
+    cndlgh  = sizeof(mqi_cond_entry_t ) *  ncond;
+
+    datalgh = vallgh + cndlgh + poollen;
+
+    if (!(del = calloc(1, sizeof(delete_statement_t) + datalgh))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    del->type  = mql_statement_delete;
+    del->table = table;
+    del->cond  = (mqi_cond_entry_t *)(del->values + (nbind + nconst));
+    del->nbind = nbind;
+
+    strpool = (void *)(del->cond + ncond);
+
+    /*
+     * copy column values, conditions and their values
+     */
+    bindv  = del->values;
+    constv = bindv + nbind;
+
+    copy_conditions_and_values(ncond, conds, del->cond,
+                               &bindv, &constv, &strpool);
+
+    return (mql_statement_t *)del;
+}
+
+mql_statement_t *mql_make_select_statement(mqi_handle_t       table,
+                                           int                rowsize,
+                                           int                ncond,
+                                           mqi_cond_entry_t  *conds,
+                                           int                ncolumn,
+                                           char             **colnames,
+                                           mqi_data_type_t   *coltypes,
+                                           int               *colsizes,
+                                           mqi_column_desc_t *columns)
+{
+    select_statement_t *sel;
+    value_t *bindv;
+    value_t *constv;
+    char    *strpool;
+    int      vallgh;
+    int      cdsclgh;
+    int      cnamlgh;
+    int      ctyplgh;
+    int      csizlgh;
+    int      cndlgh;
+    int      datalgh;
+    int      colnamlgh[MQI_COLUMN_MAX];
+    int      nbind   = 0;
+    int      nconst  = 0;
+    int      poollen = 0;
+    int      i;
+
+    MDB_CHECKARG(table != MQI_HANDLE_INVALID &&
+                 (!ncond || (ncond >= 0 && conds)) &&
+                 ncolumn > 0 && columns, NULL);
+
+    /*
+     * calculate the number of constant and bindable values
+     */
+    count_condition_values(ncond, conds, &nbind, &nconst, &poollen);
+
+    for (i = 0;   i < ncolumn;   i++)
+        poollen += (colnamlgh[i] = strlen(colnames[i]) + 1);
+
+
+    /*
+     * set up the statement structure
+     */
+    vallgh  = sizeof(     value_t     ) * (nbind + nconst);
+    cdsclgh = sizeof(mqi_column_desc_t) * (ncolumn + 1);
+    cnamlgh = sizeof(      char *     ) *  ncolumn;
+    ctyplgh = sizeof( mqi_data_type_t ) *  ncolumn;
+    csizlgh = sizeof(       int       ) *  ncolumn;
+    cndlgh  = sizeof(mqi_cond_entry_t ) *  ncond;
+
+    datalgh = vallgh + cdsclgh + cnamlgh + ctyplgh + csizlgh + cndlgh +poollen;
+
+    if (!(sel = calloc(1, sizeof(select_statement_t) + datalgh))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    sel->type     = mql_statement_select;
+    sel->table    = table;
+    sel->rowsize  = rowsize;
+    sel->ncolumn  = ncolumn;
+    sel->columns  = (mqi_column_desc_t *)(sel->values + (nbind + nconst));
+    sel->colnames = (char **)(sel->columns + (ncolumn+1));
+    sel->coltypes = (mqi_data_type_t *)(sel->colnames + ncolumn);
+    sel->colsizes = (int *)(sel->coltypes + ncolumn);
+    sel->cond     = (mqi_cond_entry_t *)(sel->colsizes + ncolumn); 
+    sel->nbind    = nbind;
+
+    strpool = (char *)(sel->cond + ncond);
+
+    if (!ncond)
+        sel->cond = NULL;
+
+    /*
+     * copy conditions and values
+     */
+    bindv  = sel->values;
+    constv = bindv + nbind;
+
+    copy_conditions_and_values(ncond, conds, sel->cond,
+                               &bindv, &constv, &strpool);
+    /*
+     * copy column descriptors, types and sizes
+     */
+    memcpy(sel->columns,  columns,  cdsclgh);
+    memcpy(sel->coltypes, coltypes, ctyplgh);
+    memcpy(sel->colsizes, colsizes, csizlgh);
+
+    /*
+     * copy column names
+     */
+    for (i = 0;   i < ncolumn;   i++) {
+        sel->colnames[i] = (char*)memcpy(strpool, colnames[i], colnamlgh[i]);
+        strpool += colnamlgh[i];
+    }
+
+    return (mql_statement_t *)sel;
+}
+
+int
+mql_bind_value(mql_statement_t *s, int id, mqi_data_type_t type, ...)
+{
+    int idx = id - 1;
+    va_list data;
+    int sts;
+
+    MDB_CHECKARG(s && id > 0, -1);
+
+    va_start(data, type);
+
+    switch (s->type) {
+
+    case mql_statement_update:
+        sts = bind_update_value((update_statement_t *)s, idx, type, data);
+        break;
+
+    case mql_statement_delete:
+        sts = bind_delete_value((delete_statement_t *)s, idx, type, data);
+        break;
+
+    case mql_statement_select:
+        sts = bind_select_value((select_statement_t *)s, idx, type, data);
+        break;
+
+    default:
+        errno = EBADRQC;
+        sts   = -1;
+        break;
+    }
+
+    va_end(data);
+
+    return sts;
+}
+
+mql_result_t *mql_exec_statement(mql_result_type_t type, mql_statement_t *s)
+{
+    mql_result_t *result;
+
+    MDB_CHECKARG(s, NULL);
+
+    switch (s->type) {
+
+    case mql_statement_show_tables:
+        result = exec_show_tables(type, (shtable_statement_t *)s);
+        break;
+
+    case mql_statement_describe:
+        result = exec_describe(type, (describe_statement_t *)s);
+        break;
+
+    case mql_statement_insert:
+        result = exec_insert((insert_statement_t *)s);
+        break;
+
+    case mql_statement_update:
+        result = exec_update((update_statement_t *)s);
+        break;
+
+    case mql_statement_delete:
+        result = exec_delete((delete_statement_t *)s);
+        break;
+
+    case mql_statement_select:
+        result = exec_select(type, (select_statement_t *)s);
+        break;
+
+    default:
+        result = mql_result_error_create(EBADRQC, "statement execution failed:"
+                                         " %s", strerror(EBADRQC));
+        break;
+    }
+
+    return result;
+}
+
+
+void mql_statement_free(mql_statement_t *s)
+{
+    free(s);
+}
+
+
+static void count_column_values(mqi_column_desc_t *cds,
+                                mqi_data_type_t   *coltypes,
+                                void              *data,
+                                int               *nbind_ret,
+                                int               *nconst_ret,
+                                int               *poollen_ret)
+{
+    mqi_column_desc_t *cd;
+    int   offs;
+    int   bidx;
+    char *str;
+    int   nbind   = *nbind_ret;
+    int   nconst  = 0;
+    int   poollen = 0;
+    int   i;
+
+    for (i = 0;   (cd = cds + i)->cindex >= 0;   i++) {
+
+        if ((offs = cd->offset) >= 0) {
+            if (coltypes[i] == mqi_varchar && (str = *(char**)(data + offs)))
+                poollen += strlen(str) + 1;
+            nconst++;
+        }
+        else {
+            if ((bidx = -(cd->offset - 1)) + 1 > nbind)
+                nbind = bidx + 1;
+        }
+    }
+
+    *nbind_ret    = nbind;
+    *nconst_ret  += nconst;
+    *poollen_ret += poollen;
+}
+
+static void count_condition_values(int               ncond,
+                                   mqi_cond_entry_t *conds,
+                                   int              *nbind_ret,
+                                   int              *nconst_ret,
+                                   int              *poollen_ret)
+{
+    mqi_cond_entry_t *ce;
+    mqi_variable_t   *var;
+    uint32_t flags;
+    int bidx;
+    char *str;
+    int nbind   = *nbind_ret;
+    int nconst  = 0;
+    int poollen = 0;
+    int i;
+
+    for (i = 0;    i < ncond;    i++) {
+        ce = conds + i;
+
+        if (ce->type == mqi_variable) {
+            var   = &ce->variable;
+            flags = var->flags;
+
+            if (!(flags & MQL_BINDABLE)) {
+                if (var->type == mqi_varchar && (str = *(var->varchar)))
+                    poollen += strlen(str) + 1;
+                nconst++;
+            }
+            else {
+                if ((bidx = MQL_BIND_INDEX(flags)) + 1 > nbind)
+                    nbind = bidx + 1;
+            }
+
+        }
+    }
+
+    *nbind_ret    = nbind;
+    *nconst_ret  += nconst;
+    *poollen_ret += poollen;
+}
+
+static void copy_column_values(int                 ncol,
+                               mqi_data_type_t    *coltypes,
+                               mqi_column_desc_t  *src_cols,
+                               mqi_column_desc_t  *dst_cols,
+                               value_t           **bindv_ptr,
+                               value_t           **constv_ptr,
+                               char              **strpool_ptr,
+                               void               *data,
+                               void               *values)
+{
+    value_t           *bindv   = *bindv_ptr;
+    value_t           *constv  = *constv_ptr;
+    void              *strpool = *strpool_ptr;
+    mqi_column_desc_t *col;
+    value_t           *val;
+    mqi_data_type_t    type;
+    int                offs;
+    void              *vptr;
+    char              *str;
+    int                len;
+    int                i;
+
+    for (i = 0;  i < ncol;  i++) {
+        type = coltypes[i];
+        *(col = dst_cols + i) = src_cols[i];
+
+        if ((offs = col->offset) < 0)
+            val  = bindv - (offs + 1);
+        else {
+            val  = constv++;
+            vptr = data + offs;
+
+            switch (type) {
+            case mqi_varchar:
+                str = *(char **)vptr;
+                len = strlen(str) + 1;
+                val->varchar = (char *)memcpy(strpool, str, len);
+                strpool += len;
+                break;
+            case mqi_integer:
+                val->integer = *(int32_t *)vptr;
+                break;
+            case mqi_unsignd:
+                val->unsignd  = *(uint32_t *)vptr;
+                break;
+            case mqi_floating: 
+                val->floating = *(double *)vptr;
+                break;
+            default:
+                break;
+            }
+        }
+
+        val->type   = type;
+        col->offset = (void *)&val->generic - values;
+    }
+
+    col = dst_cols + i;
+    col->cindex = -1;
+    col->offset = -1;
+
+    *bindv_ptr   = bindv;
+    *constv_ptr  = constv;
+    *strpool_ptr = strpool;
+}
+
+static void copy_conditions_and_values(int                ncond,
+                                       mqi_cond_entry_t  *src_conds,
+                                       mqi_cond_entry_t  *dst_conds,
+                                       value_t          **bindv_ptr,
+                                       value_t          **constv_ptr,
+                                       char             **strpool_ptr)
+{
+    value_t          *bindv   = *bindv_ptr;
+    value_t          *constv  = *constv_ptr;
+    char             *strpool = *strpool_ptr;
+    mqi_cond_entry_t *cond;
+    mqi_variable_t   *var;
+    mqi_data_type_t   type;
+    uint32_t          flags;
+    value_t          *val;
+    char             *str;
+    int               len;
+    int               i;
+
+    for (i = 0;   i < ncond;   i++) {
+        *(cond = dst_conds + i) = src_conds[i];
+
+        if (cond->type == mqi_variable) {
+            var   = &cond->variable;
+            type  = var->type;
+            flags = var->flags;
+
+            if ((flags & MQL_BINDABLE))
+                val = bindv + MQL_BIND_INDEX(flags);
+            else {
+                val = constv++;
+
+                switch (type) {
+                case mqi_varchar:
+                    str = *(var->varchar);
+                    len = strlen(str) + 1;
+                    val->varchar = (char *)memcpy(strpool, str, len);
+                    strpool += len;
+                    break;
+                case mqi_integer:
+                    val->integer = *(var->integer);
+                    break;
+                case mqi_unsignd:
+                    val->unsignd = *(var->unsignd);
+                    break;
+                case mqi_floating:
+                    val->floating = *(var->floating);
+                    break;
+                default:
+                    break;
+                }
+            }
+
+            val->type = type;
+            var->generic = (void *)&val->generic;
+        }
+    }
+
+    *bindv_ptr   = bindv;
+    *constv_ptr  = constv;
+    *strpool_ptr = strpool;
+}
+
+static mql_result_t *exec_show_tables(mql_result_type_t type,
+                                      shtable_statement_t *st)
+{
+    mql_result_t *rslt;
+    char         *names[4096];
+    int           n;
+
+    if ((n = mqi_show_tables(st->flags, names, MQI_DIMENSION(names))) < 0) {
+        rslt = mql_result_error_create(errno, "can't show tables: %s",
+                                       strerror(errno));
+    }
+    else {
+        if (!n)
+            rslt = mql_result_error_create(0, "no tables");
+        else
+            rslt = mql_result_list_create(mqi_string, n, names);
+    }
+
+    return rslt;
+}
+
+static mql_result_t *exec_describe(mql_result_type_t type,
+                                   describe_statement_t *d)
+{
+    mql_result_t     *rslt;
+    mqi_column_def_t  defs[MQI_COLUMN_MAX];
+    int               n;
+
+    if ((n = mqi_describe(d->table, defs, MQI_COLUMN_MAX)) < 0) {
+        rslt = mql_result_error_create(errno, "describe failed: %s",
+                                       strerror(errno));
+    }
+    else {
+        switch (type) {
+        case mql_result_columns:
+            rslt = mql_result_columns_create(n, defs);
+            break;
+        case mql_result_string:
+            rslt = mql_result_string_create_column_list(n, defs);
+            break;
+        default:
+            rslt = mql_result_error_create(EINVAL, "describe failed: invalid"
+                                           " result type %d", type);
+            break;
+        }
+    }
+
+    return rslt;
+}
+
+static mql_result_t *exec_insert(insert_statement_t *i)
+{
+    mql_result_t *rslt;
+    int           n;
+
+    if ((n = mqi_insert_into(i->table, i->ignore, i->columns, i->rows)) >= 0)
+        rslt = mql_result_error_create(0, "inserted %d rows", n);
+    else {
+        rslt = mql_result_error_create(errno, "insert error: %s",
+                                       strerror(errno));
+    }
+
+    return rslt;
+}
+
+static mql_result_t *exec_update(update_statement_t *u)
+{
+    mql_result_t *rslt;
+    int           n;
+
+    if ((n = mqi_update(u->table, u->cond, u->columns, u->values)) >= 0)
+        rslt = mql_result_error_create(0, "updated %d rows", n);
+    else {
+        rslt = mql_result_error_create(errno, "update error: %s",
+                                       strerror(errno));
+    }
+
+    return rslt;
+}
+
+static mql_result_t *exec_delete(delete_statement_t *d)
+{
+    mql_result_t *rslt;
+    int           n;
+
+    if ((n = mqi_delete_from(d->table, d->cond)) >= 0)
+        rslt = mql_result_error_create(0, "deleted %d rows", n);
+    else {
+        rslt = mql_result_error_create(errno, "delete error: %s",
+                                       strerror(errno));
+    }
+
+    return rslt;
+}
+
+static mql_result_t *exec_select(mql_result_type_t type, select_statement_t *s)
+{
+    mql_result_t *rslt;
+    int           maxrow;
+    int           nrow;
+    void         *rows;
+
+    if ((maxrow = mqi_get_table_size(s->table)) < 0)
+        rslt = mql_result_error_create(ENOENT, "can't access table");
+    else {
+        if (!maxrow) {
+            rows = alloca(s->rowsize);
+            nrow = 0;
+        }
+        else {
+            rows = alloca(maxrow * s->rowsize);
+            nrow = mqi_select(s->table, s->cond, s->columns,
+                              rows, s->rowsize, maxrow);
+        }
+       if (nrow < 0) {
+           rslt = mql_result_error_create(errno, "select error: %s",
+                                          strerror(errno));
+        }
+        else {
+            switch (type) {
+            case mql_result_rows:
+                rslt = mql_result_rows_create(s->ncolumn, s->columns,
+                                              s->coltypes, s->colsizes,
+                                              nrow, s->rowsize, rows);
+                break;
+            case mql_result_string:
+                rslt = mql_result_string_create_row_list(
+                                         s->ncolumn, s->colnames, s->columns,
+                                         s->coltypes, s->colsizes,
+                                         nrow, s->rowsize, rows);
+                break;
+            default:
+                rslt = mql_result_error_create(EINVAL, "select failed: invalid"
+                                               " result type %d", type);
+                break;
+            }
+        }
+    }
+
+    return rslt;
+}
+
+static int bind_update_value(update_statement_t *u,
+                             int                 idx,
+                             mqi_data_type_t     type,
+                             va_list             data)
+{
+    if (idx >= u->nbind) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    return bind_value(u->values + idx, type, data);
+}
+
+
+static int bind_delete_value(delete_statement_t *d,
+                             int                 idx,
+                             mqi_data_type_t     type,
+                             va_list             data)
+{
+    if (idx >= d->nbind) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    return bind_value(d->values + idx, type, data);
+}
+
+
+static int bind_select_value(select_statement_t *s,
+                             int                 idx,
+                             mqi_data_type_t     type,
+                             va_list             data)
+{
+    if (idx >= s->nbind) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    return bind_value(s->values + idx, type, data);
+}
+
+
+static int bind_value(value_t *v, mqi_data_type_t type, va_list data)
+{
+    if (type == v->type) {
+        switch (type) {
+        case mqi_varchar:   v->varchar  = va_arg(data, char *);     return 0;
+        case mqi_integer:   v->integer  = va_arg(data, int32_t);    return 0;
+        case mqi_unsignd:   v->unsignd  = va_arg(data, uint32_t);   return 0;
+        case mqi_floating:  v->floating = va_arg(data, double);     return 0;
+        default:                                                    break;
+        }
+    }
+
+    errno = EINVAL;
+    return -1;
+}
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/tests/Makefile.am b/src/murphy-db/tests/Makefile.am
new file mode 100644 (file)
index 0000000..96aab16
--- /dev/null
@@ -0,0 +1,47 @@
+CHECK_LIBMDB_LOG = check-libmdb.log
+CHECK_LIBMQI_LOG = check-libmqi.log
+CHECK_LIBMQL_LOG = check-libmql.log
+
+MDB_LIBS = ../mdb/libmdb.la
+MQI_LIBS = ../mqi/libmqi.la
+MQL_LIBS = ../mql/libmql.la
+
+if HAVE_CHECK
+TESTS = check-libmdb check-libmqi check-libmql
+else
+TESTS =
+endif
+
+noinst_PROGRAMS = $(TESTS)
+
+#
+# MDB tests
+#
+check_libmdb_SOURCES = check-libmdb.c
+check_libmdb_CFLAGS  = @CHECK_CFLAGS@ -I../include \
+                       -DLOGFILE=\"$(CHECK_LIBMDB_LOG)\"
+check_libmdb_LDADD   = @CHECK_LIBS@ $(MDB_LIBS)
+
+AM_CFLAGS = -g3 -O0
+
+#
+# MQI tests
+#
+check_libmqi_SOURCES = check-libmqi.c
+check_libmqi_CFLAGS  = @CHECK_CFLAGS@ -I../include \
+                       -DLOGFILE=\"$(CHECK_LIBMQI_LOG)\"
+check_libmqi_LDADD   = @CHECK_LIBS@ $(MQI_LIBS) $(MDB_LIBS) 
+
+
+#
+# MQL tests
+#
+check_libmql_SOURCES = check-libmql.c
+check_libmql_CFLAGS  = @CHECK_CFLAGS@ -I../include \
+                       -DLOGFILE=\"$(CHECK_LIBMQL_LOG)\"
+check_libmql_LDADD   = @CHECK_LIBS@ $(MQL_LIBS) $(MQI_LIBS) $(MDB_LIBS) 
+
+
+clean-local:
+       rm -f $(CHECK_LIBMDB_LOG) $(CHECK_LIBMQI_LOG) $(CHECK_LIBMQL_LOG) \
+              $(TESTS) *~
diff --git a/src/murphy-db/tests/check-libmdb.c b/src/murphy-db/tests/check-libmdb.c
new file mode 100644 (file)
index 0000000..ca1963d
--- /dev/null
@@ -0,0 +1,59 @@
+#include <check.h>
+
+#ifndef LOGFILE
+#define LOGFILE  "check_libmdb.log"
+#endif
+
+#define ADD_TEST_CASE(s,t)                      \
+    do {                                        \
+        TCase *tc = tcase_create(#t);           \
+        tcase_add_test(tc, t);                  \
+        suite_add_tcase(s, tc);                 \
+    } while (0)
+
+
+static Suite *libmdb_suite(void);
+
+
+int main()
+{
+    Suite   *s  = libmdb_suite();
+    SRunner *sr = srunner_create(s);
+    int      nf;
+
+    srunner_set_log(sr, LOGFILE);
+
+    srunner_run_all(sr, CK_NORMAL);
+
+    nf = srunner_ntests_failed(sr);
+
+    srunner_free(sr);
+    // suite_free(s);
+
+    return (nf == 0) ? 0 : 1;
+}
+
+START_TEST(create_table)
+{
+    fail_unless(1==1, "create table test");
+}
+END_TEST
+
+
+static Suite *libmdb_suite(void)
+{
+    Suite *s = suite_create("Memory Database - libmdb");
+
+    ADD_TEST_CASE(s, create_table);
+
+    return s;
+}
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/tests/check-libmqi.c b/src/murphy-db/tests/check-libmqi.c
new file mode 100644 (file)
index 0000000..d5732d6
--- /dev/null
@@ -0,0 +1,590 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <libgen.h>
+
+#include <check.h>
+
+#include <murphy-db/mqi.h>
+
+#ifndef LOGFILE
+#define LOGFILE  "check_libmqi.log"
+#endif
+
+#define PREREQUISITE(t)   t(_i)
+
+typedef struct {
+    const char  *sex;
+    const char  *first_name;
+    const char  *family_name;
+    uint32_t     id;
+    const char  *email;
+} record_t;
+
+typedef struct {
+    uint32_t       id;
+    const char    *family_name;
+    const char    *first_name;
+} query_t;
+
+
+MQI_COLUMN_DEFINITION_LIST(persons_coldefs,
+    MQI_COLUMN_DEFINITION( "sex"        , MQI_VARCHAR(6)  ),
+    MQI_COLUMN_DEFINITION( "family_name", MQI_VARCHAR(12) ),
+    MQI_COLUMN_DEFINITION( "first_name" , MQI_VARCHAR(12) ),
+    MQI_COLUMN_DEFINITION( "id"         , MQI_UNSIGNED    ),
+    MQI_COLUMN_DEFINITION( "email"      , MQI_VARCHAR(24) )
+);
+
+MQI_INDEX_DEFINITION(persons_indexdef,
+    MQI_INDEX_COLUMN("first_name")
+    MQI_INDEX_COLUMN("family_name")
+);
+
+MQI_COLUMN_SELECTION_LIST(persons_insert_columns,
+    MQI_COLUMN_SELECTOR( 0, record_t, sex         ),
+    MQI_COLUMN_SELECTOR( 2, record_t, first_name  ),
+    MQI_COLUMN_SELECTOR( 1, record_t, family_name ),
+    MQI_COLUMN_SELECTOR( 3, record_t, id          ),
+    MQI_COLUMN_SELECTOR( 4, record_t, email       )
+);
+
+MQI_COLUMN_SELECTION_LIST(persons_select_columns,
+    MQI_COLUMN_SELECTOR( 3, query_t, id         ),
+    MQI_COLUMN_SELECTOR( 1, query_t, family_name ),
+    MQI_COLUMN_SELECTOR( 2, query_t, first_name  )
+);
+
+static record_t chuck = {"male"  , "Chuck", "Norris" , 1100, "cno@texas.us"  };
+static record_t gary  = {"male"  , "Gary", "Cooper"  ,  700, "gco@heaven.org"};
+static record_t elvis = {"male"  , "Elvis", "Presley",  600, "epr@heaven.org"};
+static record_t tom   = {"male"  , "Tom", "Cruise"   ,  500, "tcr@foo.com"   };
+static record_t greta = {"female", "Greta", "Garbo"  , 2000, "gga@heaven.org"};
+static record_t rita  = {"female", "Rita", "Hayworth",   44, "rha@heaven.org"};
+
+static record_t *artists[] = {&chuck, &gary, &elvis, &tom, &greta, &rita,NULL};
+
+
+
+static int          verbose;
+static mqi_handle_t transactions[10];
+static int          txdepth;
+static mqi_handle_t persons = MQI_HANDLE_INVALID;
+static int          columns_no_in_persons = -1;
+static int          rows_no_in_persons = -1;
+
+
+static Suite *libmqi_suite(void);
+static TCase *basic_tests(void);
+static void   print_rows(int, query_t *);
+
+
+int main(int argc, char **argv)
+{
+    Suite   *s  = libmqi_suite();
+    SRunner *sr = srunner_create(s);
+    int      nf;
+    int      i;
+    
+    for (i = 1;  i < argc;  i++) {
+        if (!strcmp("-v", argv[i]))
+            verbose = 1;
+        else if (!strcmp("-f", argv[1]))
+            srunner_set_fork_status(sr, CK_NOFORK);
+        else {
+            printf("Usage: %s [-h] [-v] [-f]\n"
+                   "  -h  prints this message\n"
+                   "  -v  sets verbose mode\n"
+                   "  -f  forces no-forking mode\n",
+                   basename(argv[0]));
+            exit(strcmp("-h", argv[i]) ? 1 : 0);
+        }        
+    }
+
+    srunner_set_log(sr, LOGFILE);
+
+    srunner_run_all(sr, CK_NORMAL);
+
+    nf = srunner_ntests_failed(sr);
+
+    srunner_free(sr);
+
+    return (nf == 0) ? 0 : 1;
+}
+
+START_TEST(open_db)
+{
+    int sts = mqi_open();
+
+    fail_if(sts, "db open test");
+}
+END_TEST
+
+
+
+START_TEST(create_table_persons)
+{
+    PREREQUISITE(open_db);
+
+    persons = MQI_CREATE_TABLE("persons", MQI_TEMPORARY,
+                               persons_coldefs, persons_indexdef);
+
+    fail_if(persons == MQI_HANDLE_INVALID, "errno (%s)", strerror(errno));
+
+    columns_no_in_persons = MQI_DIMENSION(persons_coldefs) - 1;
+}
+END_TEST
+
+
+
+START_TEST(table_handle)
+{
+    mqi_handle_t handle = MQI_HANDLE_INVALID;
+
+    PREREQUISITE(create_table_persons);
+
+    handle = mqi_get_table_handle("persons");
+
+    fail_if(handle == MQI_HANDLE_INVALID, "failed to obtain handle for "
+            "'persons' (%s)", strerror(errno));
+
+    fail_if(handle != persons, "handle mismatch (0x%x vs. 0x%x)",
+            persons, handle);
+}
+END_TEST
+
+
+START_TEST(describe_persons)
+{
+    mqi_column_def_t  cols[32];
+    mqi_column_def_t *def, *col;
+    int deflgh;
+    int i,ncolumn;
+
+    PREREQUISITE(create_table_persons);
+
+    ncolumn = MQI_DESCRIBE(persons, cols);
+
+    fail_if(ncolumn < 0, "errno (%s)", strerror(errno));
+
+    fail_if(ncolumn != columns_no_in_persons, "mismatching column number "
+            "(%d vs. %d)", columns_no_in_persons, ncolumn);
+
+    if (verbose) {
+        printf("-----------------------------\n");
+        printf("name         type      length\n");
+        printf("-----------------------------\n");
+        for (i = 0;  i < ncolumn;  i++) {
+            col = cols + i;
+            printf("%-12s %-9s     %2d\n", col->name,
+                   mqi_data_type_str(col->type), col->length);
+        }
+        printf("-----------------------------\n");
+    }
+
+    for (i = 0;  i < ncolumn;  i++) {
+        def = persons_coldefs + i;
+        col = cols + i;
+
+        fail_if(strcmp(def->name, col->name), "mismatching column names @ "
+                "column %d ('%s' vs. '%s')", i, def->name, col->name);
+
+        fail_if(def->type != col->type, "mismatching column types @ "
+                "column %d (%d/'%s' vs. %d/'%s')", i,
+                def->type, mqi_data_type_str(def->type),
+                col->type, mqi_data_type_str(col->type));
+
+        switch (def->type) {
+        case mqi_varchar:   deflgh = def->length;       break;
+        case mqi_integer:   deflgh = sizeof(int32_t);   break;
+        case mqi_unsignd:   deflgh = sizeof(uint32_t);  break;
+        case mqi_floating:  deflgh = sizeof(double);    break;
+        case mqi_blob:      deflgh = def->length;       break;
+        default:            deflgh = -1;                break;
+        };
+
+        fail_if(deflgh != col->length, "mismatching column length @ "
+                "column %d (%d vs. %d)", i, deflgh, col->length);
+    }
+}
+END_TEST
+
+
+START_TEST(insert_into_persons)
+{ 
+    int n;
+
+    PREREQUISITE(create_table_persons);
+
+    n = MQI_INSERT_INTO(persons, persons_insert_columns, artists);
+
+    fail_if(n < 0, "errno (%s)", strerror(errno));
+
+    fail_if(n != MQI_DIMENSION(artists)-1, "some insertion failed. "
+            "Attempted %d succeeded %d", MQI_DIMENSION(artists)-1, n);
+
+    rows_no_in_persons = n;
+}
+END_TEST
+
+
+START_TEST(row_count_in_persons)
+{
+    int n;
+
+    PREREQUISITE(insert_into_persons);
+
+    n = mqi_get_table_size(persons);
+
+    fail_if(n < 0, "error (%s)", strerror(errno));
+
+    fail_if(n != rows_no_in_persons, "mismatch in row numbers: "
+            "Inserted %d reported %d", rows_no_in_persons, n);
+}
+END_TEST
+
+START_TEST(insert_duplicate_into_persons)
+{
+    static record_t  gary = {"male", "Gary","Cooper", 200, "gary@att.com"};
+    static record_t *duplicate[] = {&gary, NULL};
+
+    int n;
+
+    PREREQUISITE(insert_into_persons);
+
+    n = MQI_INSERT_INTO(persons, persons_insert_columns, duplicate);
+
+    fail_if(n == 1, "managed to insert a duplicate");
+
+    fail_if(n < 0 && errno != EEXIST, "error (%s)", strerror(errno));
+}
+END_TEST
+
+START_TEST(transaction_begin)
+{
+    mqi_handle_t tx;
+
+    fail_if(txdepth >= MQI_DIMENSION(transactions), "too many nested "
+            "transactions. Only %d allowed", MQI_DIMENSION(transactions));
+
+    tx = MQI_BEGIN;
+
+    fail_if(tx == MQI_HANDLE_INVALID, "error (%d)", strerror(errno));
+
+    transactions[txdepth++] = tx;
+}
+END_TEST
+
+
+START_TEST(replace_in_persons)
+{
+    static record_t  gary = {"male", "Gary","Cooper", 200, "gary@att.com"};
+    static record_t *duplicate[] = {&gary, NULL};
+
+    int n;
+
+    PREREQUISITE(insert_into_persons);
+    PREREQUISITE(transaction_begin);
+
+    n = MQI_REPLACE(persons, persons_insert_columns, duplicate);
+
+    fail_if(n < 0, "error (%s)", strerror(errno));
+
+    fail_if(n == 1, "duplicate was inserted instead of replacement");
+}
+END_TEST
+
+START_TEST(filtered_select_from_persons)
+{
+    static char     *initial = "G";
+    static uint32_t  idlimit = 200;
+
+    MQI_WHERE_CLAUSE(where,
+        MQI_GREATER( MQI_COLUMN(1), MQI_STRING_VAR(initial)   ) MQI_AND
+        MQI_GREATER( MQI_COLUMN(3), MQI_UNSIGNED_VAR(idlimit) )
+    );
+
+    query_t *r, rows[32];
+    int i, n;
+
+    PREREQUISITE(replace_in_persons);
+
+    n = MQI_SELECT(persons_select_columns, persons, where, rows);
+
+    fail_if(n < 0, "error (%s)", strerror(errno));
+
+    if (verbose)
+        print_rows(n, rows);
+
+    fail_if(n != 3, "selcted %d rows but the right number would be 3", n);
+}
+END_TEST
+
+
+START_TEST(full_select_from_persons)
+{
+    query_t *r, rows[32];
+    int i, n;
+
+    PREREQUISITE(replace_in_persons);
+
+    n = MQI_SELECT(persons_select_columns, persons, MQI_ALL, rows);
+
+    fail_if(n < 0, "error (%s)", strerror(errno));
+
+    if (verbose) {
+        printf("   id first name      family name     \n");
+        printf("--------------------------------------\n");
+
+        if (!n)
+            printf("no rows\n");
+        else {
+            for (i = 0; i < n;  i++) {
+                r = rows + i;
+                printf("%5d %-15s %-15s\n", r->id,
+                       r->first_name, r->family_name);
+            }
+        }
+
+        printf("--------------------------------------\n");
+    }
+
+    fail_if(n != 6, "selcted %d rows but the right number would be 3", n);
+}
+END_TEST
+
+
+
+START_TEST(select_from_persons_by_index)
+{
+    MQI_INDEX_VALUE(index,
+        MQI_STRING_VAL(elvis.family_name)
+        MQI_STRING_VAL(elvis.first_name)
+    );
+
+    query_t row;
+    int n;
+
+    PREREQUISITE(replace_in_persons);
+
+    n = MQI_SELECT_BY_INDEX(persons_select_columns, persons, index, &row);
+
+    fail_if(n < 0, "errno (%s)", strerror(errno));
+
+    fail_if(!n, "could not select %s %s", elvis.first_name, elvis.family_name);
+
+    fail_if(strcmp(row.first_name, elvis.first_name), "mismatching first "
+            "name ('%s' vs. '%s')", elvis.first_name, row.first_name);
+
+    fail_if(strcmp(row.family_name, elvis.family_name), "mismatching family "
+            "name ('%s' vs. '%s')", elvis.family_name, row.family_name);
+
+    fail_if(row.id != elvis.id, "mismatching id (%u vs. %u)",
+            elvis.id, row.id);
+}
+END_TEST
+
+
+
+START_TEST(update_in_persons)
+{
+    MQI_WHERE_CLAUSE(where,
+        MQI_EQUAL( MQI_COLUMN(1), MQI_STRING_VAR(elvis.family_name) ) MQI_AND
+        MQI_EQUAL( MQI_COLUMN(2), MQI_STRING_VAR(elvis.first_name ) )
+    );
+
+    static query_t kalle = {1, "Korhonen", "Kalle"};
+    
+    query_t *r, rows[32];
+    int i,n;
+    int found;
+
+    PREREQUISITE(replace_in_persons);
+
+    n = MQI_UPDATE(persons, persons_select_columns, &kalle, where);
+
+    fail_if(n  < 0, "errno (%s)", strerror(errno));
+    fail_if(n != 1, "updated %d row but supposed to just 1", n);
+
+    n = MQI_SELECT(persons_select_columns, persons, MQI_ALL, rows);
+
+    fail_if(n < 0, "select for checking failed (%s)", strerror(errno));
+
+    if (verbose)
+        print_rows(n, rows);
+
+    for (found = 0, i = 0;  i < n;  i++) {
+        r = rows + i;
+
+        fail_if(r->id == elvis.id, "found the original id %u what supposed "
+                "to change to %u", elvis.id, kalle.id);
+
+        fail_if(!strcmp(r->first_name, elvis.first_name), "found the original "
+                "first name '%s' what supposed to change to '%s'",
+                elvis.first_name, kalle.first_name);
+
+        fail_if(!strcmp(r->family_name, elvis.family_name),"found the original"
+                " family name '%s' what supposed to change to '%s'",
+                elvis.family_name, kalle.family_name);
+
+        if (r->id == kalle.id &&
+            !strcmp(r->first_name, kalle.first_name) &&
+            !strcmp(r->family_name, kalle.family_name))
+        {
+            found = 1;
+        }
+    }
+
+    fail_unless(found, "could not find the updated row");
+}
+END_TEST
+
+
+
+START_TEST(delete_from_persons)
+{
+    static uint32_t idlimit = 200;
+
+    MQI_WHERE_CLAUSE(where,
+        MQI_LESS( MQI_COLUMN(3), MQI_UNSIGNED_VAR(idlimit) )
+    );
+
+    query_t *r, rows[32];
+    int i,n;
+
+    PREREQUISITE(update_in_persons);
+
+    n = MQI_DELETE(persons, where);
+
+    fail_if(n  < 0, "errno (%s)", strerror(errno));
+    fail_if(n != 2, "deleted %d rows but sopposed to 2", n);
+
+    n = MQI_SELECT(persons_select_columns, persons, MQI_ALL, rows);
+
+    fail_if(n < 0, "verification select failed (%s)", strerror(errno));
+
+    if (verbose)
+        print_rows(n, rows);
+
+    for (i = 0;  i < n;  i++) {
+        r = rows + i;
+
+        fail_if(r->id < idlimit, "found row with id %u what is smaller than "
+                "the limit %u", r->id, idlimit);
+    }
+}
+END_TEST
+
+
+
+START_TEST(transaction_rollback)
+{
+    record_t *a;
+    query_t *r, rows[32];
+    int i,j,n;
+    int sts;
+    int found;
+
+    PREREQUISITE(delete_from_persons);
+
+    fail_unless(txdepth > 0, "actually there is no transaction");
+
+    sts = MQI_ROLLBACK(transactions[--txdepth]);
+
+    fail_if(sts < 0, "errno (%s)", strerror(errno));
+
+    n = MQI_SELECT(persons_select_columns, persons, MQI_ALL, rows);
+
+    fail_if(n < 0, "verification select failed (%s)", strerror(errno));
+
+    if (verbose)
+        print_rows(n, rows);
+
+    fail_if(n != MQI_DIMENSION(artists)-1, "mismatching row numbers: currently"
+            " %d supposed to be %d", n, MQI_DIMENSION(artists)-1);
+
+
+    for (i = 0;  i < n;  i++) {
+        r = rows + i;
+
+        for (found = 0, j = 0;  j < MQI_DIMENSION(artists)-1;  j++) {
+            a = artists[j];
+
+            if (a->id == r->id &&
+                !strcmp(a->first_name, r->first_name) &&
+                !strcmp(a->family_name, r->family_name))
+            {
+                found = 1;
+                break;
+            }
+        }
+
+        fail_unless(found, "after rolling back can't find %s %s (id %u) "
+                    "any more", r->first_name, r->family_name, r->id);
+    }
+}
+END_TEST
+
+
+static Suite *libmqi_suite(void)
+{
+    Suite *s = suite_create("Murphy Query Interface - libmqi");
+    TCase *tc_basic = basic_tests();
+
+    suite_add_tcase(s, tc_basic);
+
+    return s;
+}
+
+static TCase *basic_tests(void)
+{
+    TCase *tc = tcase_create("basic tests");
+
+    tcase_add_test(tc, open_db);
+    tcase_add_test(tc, create_table_persons);
+    tcase_add_test(tc, table_handle);
+    tcase_add_test(tc, describe_persons);
+    tcase_add_test(tc, insert_into_persons);
+    tcase_add_test(tc, row_count_in_persons);
+    tcase_add_test(tc, insert_duplicate_into_persons);
+    tcase_add_test(tc, replace_in_persons);
+    tcase_add_test(tc, filtered_select_from_persons);
+    tcase_add_test(tc, full_select_from_persons);
+    tcase_add_test(tc, select_from_persons_by_index);
+    tcase_add_test(tc, update_in_persons);
+    tcase_add_test(tc, delete_from_persons);
+    tcase_add_test(tc, transaction_rollback);
+
+    return tc;
+}
+
+static void print_rows(int n, query_t *rows)
+{
+    query_t *r;
+    int i;
+
+    printf("   id first name      family name     \n");
+    printf("--------------------------------------\n");        
+    
+    if (!n)
+        printf("no rows\n");
+    else {
+        for (i = 0; i < n;  i++) {
+            r = rows + i;
+            printf("%5d %-15s %-15s\n", r->id,
+                   r->first_name, r->family_name);
+        }
+    }
+
+    printf("--------------------------------------\n");
+}
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/tests/check-libmql.c b/src/murphy-db/tests/check-libmql.c
new file mode 100644 (file)
index 0000000..13fb847
--- /dev/null
@@ -0,0 +1,669 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <libgen.h>
+
+#include <check.h>
+
+#include <murphy-db/mqi.h>
+#include <murphy-db/mql.h>
+
+
+#ifndef LOGFILE
+#define LOGFILE  "check_libmql.log"
+#endif
+
+#define PREREQUISITE(t)   t(_i)
+
+typedef struct {
+    const char  *sex;
+    const char  *first_name;
+    const char  *family_name;
+    uint32_t     id;
+    const char  *email;
+} record_t;
+
+static mqi_column_def_t persons_columns[] = {
+    {"sex"        , mqi_varchar,  6, 0},
+    {"family_name", mqi_varchar, 12, 0},
+    {"first_name" , mqi_varchar, 12, 0},
+    {"id"         , mqi_unsignd,  4, 0},
+    {"email"      , mqi_varchar, 24, 0}
+};
+static int persons_ncolumn = MQI_DIMENSION(persons_columns);
+
+static record_t persons_rows[] = {
+    {"male"  , "Chuck", "Norris" , 1100, "cno@texas.us"  },
+    {"male"  , "Gary", "Cooper"  ,  700, "gco@heaven.org"},
+    {"male"  , "Elvis", "Presley",  600, "epr@heaven.org"},
+    {"male"  , "Tom", "Cruise"   ,  500, "tcr@foo.com"   },
+    {"female", "Greta", "Garbo"  , 2000, "gga@heaven.org"},
+    {"female", "Rita", "Hayworth",   44, "rha@heaven.org"}
+};
+static int persons_nrow = MQI_DIMENSION(persons_rows);
+
+
+static int verbose;
+static struct {
+    mql_statement_t *filtered_select;
+    mql_statement_t *full_select;
+    mql_statement_t *update;
+    mql_statement_t *delete;
+    mql_statement_t *insert;
+} persons;
+
+
+static Suite *libmql_suite(void);
+static TCase *basic_tests(void);
+
+
+
+int main(int argc, char **argv)
+{
+    Suite   *s  = libmql_suite();
+    SRunner *sr = srunner_create(s);
+    int      nf;
+    int      i;
+
+    for (i = 1;  i < argc;  i++) {
+        if (!strcmp("-v", argv[i]))
+            verbose = 1;
+        else if (!strcmp("-f", argv[1]))
+            srunner_set_fork_status(sr, CK_NOFORK);
+        else {
+            printf("Usage: %s [-h] [-v] [-f]\n"
+                   "  -h  prints this message\n"
+                   "  -v  sets verbose mode\n"
+                   "  -f  forces no-forking mode\n",
+                   basename(argv[0]));
+            exit(strcmp("-h", argv[i]) ? 1 : 0);
+        }        
+    }
+
+    srunner_set_log(sr, LOGFILE);
+
+    srunner_run_all(sr, CK_NORMAL);
+
+    nf = srunner_ntests_failed(sr);
+
+    srunner_free(sr);
+    // suite_free(s);
+
+    return (nf == 0) ? 0 : 1;
+}
+
+
+START_TEST(open_db)
+{
+    int sts = mqi_open();
+
+    fail_if(sts, "db open test");
+}
+END_TEST
+
+
+
+START_TEST(create_table_persons)
+{
+    mql_result_t *r;
+
+    PREREQUISITE(open_db);
+
+    r = mql_exec_string(mql_result_string,
+                        "CREATE TEMPORARY TABLE persons ("
+                        "   sex          VARCHAR(6), "
+                        "   family_name  VARCHAR(12),"
+                        "   first_name   VARCHAR(12),"
+                        "   id           UNSIGNED,   "
+                        "   email        VARCHAR(24) "
+                        ")"
+    );
+
+    fail_unless(mql_result_is_success(r), "error: %s",
+                mql_result_error_get_message(r));
+
+    mql_result_free(r);
+}
+END_TEST
+
+
+
+START_TEST(describe_persons)
+{
+    mql_result_type_t rt = verbose ? mql_result_string : mql_result_columns;
+    mqi_column_def_t *cd;
+    mql_result_t *r;
+    mqi_data_type_t type;
+    const char *name;
+    int length;
+    int i,n;
+
+    PREREQUISITE(create_table_persons);
+
+    r = mql_exec_string(rt, "DESCRIBE persons");
+
+    fail_unless(mql_result_is_success(r), "error: %s",
+                mql_result_error_get_message(r));
+
+    if (verbose)
+        printf("%s\n", mql_result_string_get(r));
+    else {
+        n = mql_result_columns_get_column_count(r);
+
+        fail_if(n  < 1, "invalid column count %d", n);
+        fail_if(n != persons_ncolumn, "coulumn count is %d but "
+                "it supposed to be %d", n, persons_ncolumn); 
+
+        for (i = 0;   i < n;   i++) {
+            cd = persons_columns + i;
+            name = mql_result_columns_get_name(r, i);
+            type = mql_result_columns_get_type(r, i);
+            length = mql_result_columns_get_length(r, i); 
+
+            fail_if(strcmp(name, cd->name), "column%d name mismatch "
+                    "('%s' vs. '%s')", i, cd->name, name);
+
+            fail_if(type != cd->type, "column%d type mismatch (%s vs. %s)",
+                    i, mqi_data_type_str(cd->type), mqi_data_type_str(type));
+
+            fail_if(length != cd->length, "column%d length mismatch "
+                    "(%d vs. %d)", i, cd->length, length);
+        }
+    }
+
+    mql_result_free(r);
+}
+END_TEST
+
+
+
+
+START_TEST(create_index_on_persons)
+{
+    mql_result_t *r;
+
+    PREREQUISITE(create_table_persons);
+
+    r = mql_exec_string(mql_result_string,
+                        "CREATE INDEX ON persons (family_name, first_name)");
+
+    fail_unless(mql_result_is_success(r), "error: %s",
+                mql_result_error_get_message(r));
+}
+END_TEST
+
+
+
+START_TEST(insert_into_persons)
+{
+    mql_result_t *r;
+    record_t *p;
+    char statement[512];
+    int i;
+
+    PREREQUISITE(create_index_on_persons);
+
+    for (i = 0;  i < persons_nrow;  i++) {
+        p = persons_rows + i;
+
+        snprintf(statement, sizeof(statement),
+                 "INSERT INTO persons VALUES ('%s', '%s', '%s', %u, '%s')",
+                 p->sex, p->family_name, p->first_name, p->id, p->email);
+
+        r = mql_exec_string(mql_result_string, statement);
+
+        fail_unless(mql_result_is_success(r), "error @ row%d: %s",
+                    i, mql_result_error_get_message(r)); 
+    }
+}
+END_TEST
+
+
+START_TEST(make_persons)
+{
+    static int done;
+
+    if (!done) {
+        PREREQUISITE(insert_into_persons);
+        done = 1;
+    }
+}
+END_TEST
+
+
+START_TEST(precompile_filtered_person_select)
+{
+    mql_statement_t *stmnt;
+
+    PREREQUISITE(make_persons);
+
+    stmnt = mql_precompile("SELECT id, first_name, family_name FROM persons"
+                           " WHERE id > %u & id <= %u");
+
+    fail_if(!stmnt, "precompilation error (%s)", strerror(errno));
+
+    persons.filtered_select = stmnt;
+}
+END_TEST
+
+
+
+START_TEST(precompile_full_person_select)
+{
+    mql_statement_t *stmnt;
+
+    PREREQUISITE(make_persons);
+
+    if (!persons.full_select) {
+        stmnt = mql_precompile("SELECT id, first_name, family_name"
+                               " FROM persons");
+
+        fail_if(!stmnt, "precompilation error (%s)", strerror(errno));
+
+        persons.full_select = stmnt;
+    }
+}
+END_TEST
+
+
+
+START_TEST(precompile_update_persons)
+{
+    mql_statement_t *stmnt;
+
+    PREREQUISITE(make_persons);
+
+    if (!persons.update) {
+        stmnt = mql_precompile("UPDATE persons "
+                               "  SET family_name = %s,"
+                               "      first_name  = %s"
+                               "  WHERE id = %u");
+        
+        fail_if(!stmnt, "precompilation error (%s)", strerror(errno));
+
+        persons.update = stmnt;
+    }
+}
+END_TEST
+
+
+
+START_TEST(precompile_delete_from_persons)
+{
+    mql_statement_t *stmnt;
+
+    PREREQUISITE(make_persons);
+
+    if (!persons.delete) {
+        stmnt = mql_precompile("DELETE FROM persons WHERE family_name = %s");
+
+        fail_if(!stmnt, "precompilation error (%s)", strerror(errno));
+
+        persons.delete = stmnt;
+    }
+}
+END_TEST
+
+
+
+START_TEST(precompile_insert_into_persons)
+{
+    mql_statement_t *stmnt;
+
+    PREREQUISITE(make_persons);
+
+    if (!persons.insert) {
+        stmnt = mql_precompile("INSERT INTO persons VALUES ("
+                               " 'male', 'Baltzar','Veijo', 855, 'vba@pdf.org'"
+                               ")");
+
+        fail_if(!stmnt, "precompilation error (%s)", strerror(errno));
+
+        persons.insert = stmnt;
+    }
+}
+END_TEST
+
+
+
+START_TEST(exec_precompiled_filtered_select_from_persons)
+{
+    mql_result_type_t rt = verbose ? mql_result_string : mql_result_rows;
+    mql_result_t *r;
+    int n;
+
+    PREREQUISITE(precompile_filtered_person_select);
+
+    if (mql_bind_value(persons.filtered_select, 1, mqi_unsignd,  200) < 0 ||
+        mql_bind_value(persons.filtered_select, 2, mqi_unsignd, 1100) < 0  )
+    {
+        fail("bind error (%s)", strerror(errno));
+    }
+
+    r = mql_exec_statement(rt, persons.filtered_select);
+
+    fail_unless(mql_result_is_success(r), "exec error: %s",
+                mql_result_error_get_message(r));
+
+    if (verbose)
+        printf("%s\n", mql_result_string_get(r));
+    else {
+        if ((n = mql_result_rows_get_row_count(r)) != 4)
+            fail("row number mismatch (4 vs. %d)", n);
+    }
+
+    mql_result_free(r);
+
+    mql_statement_free(persons.filtered_select);
+    persons.filtered_select = NULL;
+}
+END_TEST
+
+
+
+START_TEST(exec_precompiled_full_select_from_persons)
+{
+    mql_result_type_t rt = verbose ? mql_result_string : mql_result_rows;
+    mql_result_t *r;
+    int n;
+
+    PREREQUISITE(precompile_full_person_select);
+
+    r = mql_exec_statement(rt, persons.full_select);
+
+    fail_unless(mql_result_is_success(r), "exec error: %s",
+                mql_result_error_get_message(r));
+
+    if (verbose)
+        printf("%s\n", mql_result_string_get(r));
+    else {
+        if ((n = mql_result_rows_get_row_count(r)) != persons_nrow)
+            fail("row number mismatch (%d vs. %d)", persons_nrow, n);
+    }
+
+    mql_result_free(r);
+
+    mql_statement_free(persons.full_select);
+    persons.full_select = NULL;
+}
+END_TEST
+
+START_TEST(exec_precompiled_update_persons)
+{
+    static uint32_t    id         = 2000;
+    static const char *new_first  = "Marilyn";
+    static const char *new_family = "Monroe";
+
+    PREREQUISITE(precompile_update_persons);
+
+    mql_result_type_t rt = verbose ? mql_result_string : mql_result_rows;
+    mql_result_t *r;
+    record_t *p;
+    const char *first;
+    const char *family;
+    int updated;
+    int i, n;
+
+    /* 2000: Greta Garbo => Marilyn Monroe */
+    if (mql_bind_value(persons.update, 1, mqi_string ,  new_first) < 0 ||
+        mql_bind_value(persons.update, 2, mqi_string , new_family) < 0 ||
+        mql_bind_value(persons.update, 3, mqi_unsignd,         id) < 0   )
+    {
+        fail("bind error (%s)", strerror(errno));
+    }
+
+
+    r = mql_exec_statement(mql_result_string, persons.update);
+    
+    fail_unless(mql_result_is_success(r), "exec error: %s",
+                mql_result_error_get_message(r));
+
+    mql_result_free(r);
+
+    /* verification */
+    r = mql_exec_string(rt, "SELECT id, first_name, family_name FROM persons");
+
+    fail_unless(mql_result_is_success(r), "exec error @ verifying select: %s",
+                mql_result_error_get_message(r));
+
+    if (verbose)
+        printf("%s\n", mql_result_string_get(r));
+    else {
+        for (p = NULL, i = 0;  i < persons_nrow;  i++) {
+            if (persons_rows[i].id == id) {
+                p = persons_rows + i;
+                break;
+            }
+        }
+
+        n = mql_result_rows_get_row_count(r);
+
+        for (updated = 0, i = 0;   i < n;   i++) {
+            first  = mql_result_rows_get_string(r, 1, i, NULL,0);
+            family = mql_result_rows_get_string(r, 2, i, NULL,0);
+
+            if (p) {
+                fail_if(!strcmp(first, p->first_name), "found original "
+                        "first name '%s'", p->first_name);
+                fail_if(!strcmp(family, p->family_name), "found original "
+                        "family name '%s'", p->family_name);
+            }
+            else {
+                fail_if(!strcmp(first, new_first), "found new "
+                        "first name '%s'", first);
+                fail_if(!strcmp(family, new_family), "found new "
+                        "family name '%s'", family);
+            }
+
+            if (id == mql_result_rows_get_unsigned(r, 0, i)) {
+                if (strcmp(first, new_first) || strcmp(family, new_family)) {
+                    updated = 1;
+                }
+            }
+        }
+
+        if (p)
+            fail_unless(updated, "result is success but no actual update");
+        else
+            fail_unless(!updated, "update happened but it not supposed to");
+    }
+
+    mql_result_free(r);
+
+
+    mql_statement_free(persons.update);
+    persons.update = NULL;
+}
+END_TEST
+
+
+START_TEST(exec_precompiled_delete_from_persons)
+{
+    const char *del_family = "Cruise";
+
+    mql_result_type_t rt = verbose ? mql_result_string : mql_result_rows;
+    mql_result_t *r;
+    record_t *p;
+    uint32_t id;
+    const char *first;
+    const char *family;
+    int i,n;
+
+    PREREQUISITE(precompile_delete_from_persons);
+
+    /* delete Tom Cruise */
+    if (mql_bind_value(persons.delete, 1, mqi_string , del_family) < 0)
+        fail("bind error (%s)", strerror(errno));
+
+    r = mql_exec_statement(mql_result_string, persons.delete);
+    
+    fail_unless(mql_result_is_success(r), "exec error: %s",
+                mql_result_error_get_message(r));
+
+    mql_result_free(r);
+
+
+    /* verification */
+    r = mql_exec_string(rt, "SELECT id, first_name, family_name FROM persons");
+
+    fail_unless(mql_result_is_success(r), "exec error @ verifying select: %s",
+                mql_result_error_get_message(r));
+
+    if (verbose)
+        printf("%s\n", mql_result_string_get(r));
+    else {
+        for (p = NULL, i = 0;  i < persons_nrow;  i++) {
+            if (!strcmp(persons_rows[i].family_name, del_family)) {
+                p = persons_rows + i;
+                break;
+            }
+        }
+        n = mql_result_rows_get_row_count(r);
+
+        for (i = 0;   i < n;   i++) {
+            id     = mql_result_rows_get_unsigned(r, 0, i);
+            first  = mql_result_rows_get_string(r, 1, i, NULL,0);
+            family = mql_result_rows_get_string(r, 2, i, NULL,0);
+
+            if (p) {
+                /* supposed to be deleted */
+                fail_if(id == p->id, "found id %u of the presumably "
+                        "deleted row", id);
+                fail_if(!strcmp(first, p->first_name), "found first name '%s' "
+                        "of the presumably deleted row", first);
+                fail_if(!strcmp(family, p->family_name), "found family name "
+                        "'%s' of the presumably deleted row", family);
+            }
+            else {
+                /* nothing supposed to be deleted */
+                fail_if(!strcmp(family, del_family), "found family name '%s'"
+                        "what not supposed to be there", family);
+            }
+        }
+   }
+
+    mql_result_free(r);
+
+    mql_statement_free(persons.delete);
+    persons.delete = NULL;
+}
+END_TEST
+
+
+
+START_TEST(exec_precompiled_insert_into_persons)
+{
+    mql_result_type_t rt = verbose ? mql_result_string : mql_result_rows;
+    mql_result_t *r;
+    record_t *p;
+    const char *first;
+    const char *family;
+    int inserted;
+    int i,n;
+
+    PREREQUISITE(precompile_insert_into_persons);
+
+
+    for (p = NULL, i = 0;  i < persons_nrow;  i++) {
+        if (!strcmp(persons_rows[i].family_name, "Baltzar") &&
+            !strcmp(persons_rows[i].first_name ,   "Veijo")   )
+        {
+            p = persons_rows + i;
+            break;
+        }
+    }
+
+    /* insert Veijo Baltzar */
+    r = mql_exec_statement(mql_result_string, persons.insert);
+    
+    if (p)
+        fail_if(mql_result_is_success(r), "manage to insert a duplicate");
+    else {
+        fail_unless(mql_result_is_success(r), "exec error: %s",
+                    mql_result_error_get_message(r));
+    }
+
+    mql_result_free(r);
+
+
+    /* verification */
+    r = mql_exec_string(rt, "SELECT id, first_name, family_name FROM persons");
+
+    fail_unless(mql_result_is_success(r), "exec error @ verifying select: %s",
+                mql_result_error_get_message(r));
+
+    if (verbose)
+        printf("%s\n", mql_result_string_get(r));
+    else {
+        if (!p) {
+            n = mql_result_rows_get_row_count(r);
+        
+            for (inserted = 0, i = 0;   i < n;   i++) {
+                first  = mql_result_rows_get_string(r, 1, i, NULL,0);
+                family = mql_result_rows_get_string(r, 2, i, NULL,0);
+                
+                if (!strcmp(first, "Veijo") && !strcmp(family, "Baltzar")) {
+                    inserted = 1;
+                    break;
+                }
+            }
+
+            fail_unless(inserted, "Veijo does not seem to be an the artist");
+        }
+    }
+
+
+
+
+    mql_result_free(r);
+
+
+    mql_statement_free(persons.insert);
+    persons.insert = NULL;
+}
+END_TEST
+
+static Suite *libmql_suite(void)
+{
+    Suite *s = suite_create("Murphy Query Language - libmql");
+    TCase *tc_basic = basic_tests();
+
+    suite_add_tcase(s, tc_basic);
+
+    return s;
+}
+
+
+static TCase *basic_tests(void)
+{
+    TCase *tc = tcase_create("basic tests");
+
+    tcase_add_test(tc, open_db);
+    tcase_add_test(tc, create_table_persons);
+    tcase_add_test(tc, describe_persons);
+    tcase_add_test(tc, create_index_on_persons);
+    tcase_add_test(tc, insert_into_persons);
+    tcase_add_test(tc, precompile_filtered_person_select);
+    tcase_add_test(tc, precompile_full_person_select);
+    tcase_add_test(tc, precompile_update_persons);
+    tcase_add_test(tc, precompile_delete_from_persons);
+    tcase_add_test(tc, precompile_insert_into_persons);
+    tcase_add_test(tc, exec_precompiled_filtered_select_from_persons);
+    tcase_add_test(tc, exec_precompiled_full_select_from_persons);
+    tcase_add_test(tc, exec_precompiled_update_persons);
+    tcase_add_test(tc, exec_precompiled_delete_from_persons);
+    tcase_add_test(tc, exec_precompiled_insert_into_persons);
+
+    return tc;
+}
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */