merge new-evsql into new structure
authorTero Marttila <terom@fixme.fi>
Sun, 08 Mar 2009 01:33:45 +0200
changeset 58 02e539965ef4
parent 57 527d23bf6441 (diff)
parent 55 0b92d553400a (current diff)
child 59 54a2e6be81a7
merge new-evsql into new structure
.hgignore
include/evsql.h
src/CMakeLists.txt
src/core.c
src/evsql/evsql.h
src/evsql/query.c
src/evsql/result.c
src/internal.h
src/query.c
src/result.c
src/util.c
--- a/.hgignore	Sat Dec 13 20:58:27 2008 +0200
+++ b/.hgignore	Sun Mar 08 01:33:45 2009 +0200
@@ -1,7 +1,5 @@
 type: re
 
-^bin/
-^obj/
-^build/deps/
+^build
 \.[^/]+.sw[op]$
 ^doc/(\w+/)?html$
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CMakeLists.txt	Sun Mar 08 01:33:45 2009 +0200
@@ -0,0 +1,15 @@
+# policy
+cmake_minimum_required (VERSION 2.6)
+
+# project attributes
+project (evsql C)
+
+# cmake paths
+set(CMAKE_MODULE_PATH "${evsql_SOURCE_DIR}/cmake/Modules/")
+
+# dependancies
+find_package(LibEvent REQUIRED)
+
+# add the src subdir
+add_subdirectory (src)
+
--- a/Makefile	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,94 +0,0 @@
-LIBEVENT_PATH = ../libs/libevent-dev
-LIBFUSE_PATH = ../opt
-
-LIBRARY_PATHS = -L${LIBEVENT_PATH}/lib -L${LIBFUSE_PATH}/lib
-INCLUDE_PATHS = -I${LIBEVENT_PATH}/include -I${LIBFUSE_PATH}/include
-LDLIBS = -levent -lfuse -lpq
-
-# default is test
-ifndef MODE
-MODE = test
-endif
-
-ifeq ($(MODE), debug)
-MODE_CFLAGS = -g -DDEBUG_ENABLED
-else ifeq ($(MODE), dev)
-MODE_CFLAGS = -g
-else ifeq ($(MODE), test)
-MODE_CFLAGS = -g -DINFO_DISABLED
-else ifeq ($(MODE), release)
-MODE_CFLAGS = -DINFO_DISABLED -O2
-endif
-
-# XXX: ugh... use `pkg-config fuse`
-DEFINES = -D_FILE_OFFSET_BITS=64
-FIXED_CFLAGS = -Wall -std=gnu99
-
-BIN_NAMES = helloworld hello simple_hello evpq_test url_test dbfs evsql_test
-BIN_PATHS = $(addprefix bin/,$(BIN_NAMES))
-
-# modules
-module_objs = $(patsubst src/%.c,obj/%.o,$(wildcard src/$(1)/*.c))
-
-# complex modules
-CORE_OBJS = obj/lib/log.o obj/lib/signals.o
-EVSQL_OBJS = $(call module_objs,evsql) obj/evpq.o
-DBFS_OBJS = $(call module_objs,dbfs) obj/dirbuf.o 
-
-# first target
-all: ${BIN_PATHS}
-
-# binaries
-bin/helloworld: 
-bin/hello: obj/evfuse.o obj/dirbuf.o ${CORE_OBJS}
-bin/simple_hello: obj/evfuse.o obj/dirbuf.o obj/simple.o ${CORE_OBJS}
-bin/evpq_test: obj/evpq.o obj/lib/log.o
-bin/url_test: obj/lib/url.o obj/lib/lex.o obj/lib/log.o
-bin/dbfs: ${DBFS_OBJS} ${EVSQL_OBJS} obj/evfuse.o ${CORE_OBJS}
-bin/evsql_test: ${EVSQL_OBJS} ${CORE_OBJS}
-
-# computed
-LDFLAGS = ${LIBRARY_PATHS}
-
-CPPFLAGS = ${INCLUDE_PATHS} ${DEFINES}
-CFLAGS = ${MODE_CFLAGS} ${FIXED_CFLAGS}
-
-SRC_PATHS = $(wildcard src/*.c) $(wildcard src/*/*.c)
-SRC_NAMES = $(patsubst src/%,%,$(SRC_PATHS))
-SRC_DIRS = $(dir $(SRC_NAMES))
-
-# other targets
-clean :
-	-rm obj/*.o obj/*/*.o
-	-rm bin/* 
-	-rm build/deps/*.d build/deps/*/*.d
-
-clean-deps:
-	-rm build/deps/*/*.d 
-	-rm build/deps/*.d
-
-#obj-dirs: 
-#	python build/make_obj_dirs.py $(BIN_PATHS)
-
-build/deps/%.d : src/%.c
-	@set -e; rm -f $@; \
-	 $(CC) -MM -MT __ $(CPPFLAGS) $< > $@.$$$$; \
-	 sed 's,__[ :]*,obj/$*.o $@ : ,g' < $@.$$$$ > $@; \
-	 rm -f $@.$$$$
-
-include $(SRC_NAMES:%.c=build/deps/%.d)
-
-obj/%.o : src/%.c
-	$(CC) -c $(CPPFLAGS) $(CFLAGS) $< -o $@
-
-bin/% : obj/%.o
-	$(CC) $(LDFLAGS) $+ $(LOADLIBES) $(LDLIBS) -o $@
-
-# documentation
-DOXYGEN_PATH = /usr/bin/doxygen
-DOXYGEN_CONF_PATH = doc/doxygen.conf
-DOXYGEN_OUTPUT_FILE = doc/html/index.html
-
-docs :
-	${DOXYGEN_PATH} ${DOXYGEN_CONF_PATH}
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmake/Modules/FindLibEvent.cmake	Sun Mar 08 01:33:45 2009 +0200
@@ -0,0 +1,27 @@
+# Find libevent
+# Once done, this will define:
+#
+#   LibEvent_FOUND
+#   LibEvent_INCLUDE_DIRS
+#   LibEvent_LIBRARIES
+#
+# Currently, this only supports libevent-svn (i.e. 1.5/2.0), so it's kind of useless for real use :)
+
+include (LibFindMacros)
+
+# include dir
+find_path (LibEvent_INCLUDE_DIR
+    NAMES "event2/event.h"
+    PATHS "$ENV{LIBEVENT_PREFIX}/include"
+)
+
+# library
+find_library (LibEvent_LIBRARY
+    NAMES "event"
+    PATHS "$ENV{LIBEVENT_PREFIX}/lib"
+)
+
+# set the external vars
+set (LibEvent_PROCESS_INCLUDES LibEvent_INCLUDE_DIR)
+set (LibEvent_PROCESS_LIBS LibEvent_LIBRARY)
+libfind_process (LibEvent)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmake/Modules/LibFindMacros.cmake	Sun Mar 08 01:33:45 2009 +0200
@@ -0,0 +1,99 @@
+# Works the same as find_package, but forwards the "REQUIRED" and "QUIET" arguments
+# used for the current package. For this to work, the first parameter must be the
+# prefix of the current package, then the prefix of the new package etc, which are
+# passed to find_package.
+macro (libfind_package PREFIX)
+  set (LIBFIND_PACKAGE_ARGS ${ARGN})
+  if (${PREFIX}_FIND_QUIETLY)
+    set (LIBFIND_PACKAGE_ARGS ${LIBFIND_PACKAGE_ARGS} QUIET)
+  endif (${PREFIX}_FIND_QUIETLY)
+  if (${PREFIX}_FIND_REQUIRED)
+    set (LIBFIND_PACKAGE_ARGS ${LIBFIND_PACKAGE_ARGS} REQUIRED)
+  endif (${PREFIX}_FIND_REQUIRED)
+  find_package(${LIBFIND_PACKAGE_ARGS})
+endmacro (libfind_package)
+
+# Damn CMake developers made the UsePkgConfig system deprecated in the same release (2.6)
+# where they added pkg_check_modules. Consequently I need to support both in my scripts
+# to avoid those deprecated warnings. Here's a helper that does just that.
+# Works identically to pkg_check_modules, except that no checks are needed prior to use.
+macro (libfind_pkg_check_modules PREFIX PKGNAME)
+  if (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4)
+    include(UsePkgConfig)
+    pkgconfig(${PKGNAME} ${PREFIX}_INCLUDE_DIRS ${PREFIX}_LIBRARY_DIRS ${PREFIX}_LDFLAGS ${PREFIX}_CFLAGS)
+  else (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4)
+    find_package(PkgConfig)
+    if (PKG_CONFIG_FOUND)
+      pkg_check_modules(${PREFIX} ${PKGNAME})
+    endif (PKG_CONFIG_FOUND)
+  endif (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4)
+endmacro (libfind_pkg_check_modules)
+
+# Do the final processing once the paths have been detected.
+# If include dirs are needed, ${PREFIX}_PROCESS_INCLUDES should be set to contain
+# all the variables, each of which contain one include directory.
+# Ditto for ${PREFIX}_PROCESS_LIBS and library files.
+# Will set ${PREFIX}_FOUND, ${PREFIX}_INCLUDE_DIRS and ${PREFIX}_LIBRARIES.
+# Also handles errors in case library detection was required, etc.
+macro (libfind_process PREFIX)
+  # Skip processing if already processed during this run
+  if (NOT ${PREFIX}_FOUND)
+    # Start with the assumption that the library was found
+    set (${PREFIX}_FOUND TRUE)
+
+    # Process all includes and set _FOUND to false if any are missing
+    foreach (i ${${PREFIX}_PROCESS_INCLUDES})
+      if (${i})
+        set (${PREFIX}_INCLUDE_DIRS ${${PREFIX}_INCLUDE_DIRS} ${${i}})
+        mark_as_advanced(${i})
+      else (${i})
+        set (${PREFIX}_FOUND FALSE)
+      endif (${i})
+    endforeach (i)
+
+    # Process all libraries and set _FOUND to false if any are missing
+    foreach (i ${${PREFIX}_PROCESS_LIBS})
+      if (${i})
+        set (${PREFIX}_LIBRARIES ${${PREFIX}_LIBRARIES} ${${i}})
+        mark_as_advanced(${i})
+      else (${i})
+        set (${PREFIX}_FOUND FALSE)
+      endif (${i})
+    endforeach (i)
+
+    # Print message and/or exit on fatal error
+    if (${PREFIX}_FOUND)
+      if (NOT ${PREFIX}_FIND_QUIETLY)
+        message (STATUS "Found ${PREFIX} ${${PREFIX}_VERSION}")
+      endif (NOT ${PREFIX}_FIND_QUIETLY)
+    else (${PREFIX}_FOUND)
+      if (${PREFIX}_FIND_REQUIRED)
+        foreach (i ${${PREFIX}_PROCESS_INCLUDES} ${${PREFIX}_PROCESS_LIBS})
+          message("${i}=${${i}}")
+        endforeach (i)
+        message (FATAL_ERROR "Required library ${PREFIX} NOT FOUND.\nInstall the library (dev version) and try again. If the library is already installed, use ccmake to set the missing variables manually.")
+      endif (${PREFIX}_FIND_REQUIRED)
+    endif (${PREFIX}_FOUND)
+  endif (NOT ${PREFIX}_FOUND)
+endmacro (libfind_process)
+
+macro(libfind_library PREFIX basename)
+  set(TMP "")
+  if(MSVC80)
+    set(TMP -vc80)
+  endif(MSVC80)
+  if(MSVC90)
+    set(TMP -vc90)
+  endif(MSVC90)
+  set(${PREFIX}_LIBNAMES ${basename}${TMP})
+  if(${ARGC} GREATER 2)
+    set(${PREFIX}_LIBNAMES ${basename}${TMP}-${ARGV2})
+    string(REGEX REPLACE "\\." "_" TMP ${${PREFIX}_LIBNAMES})
+    set(${PREFIX}_LIBNAMES ${${PREFIX}_LIBNAMES} ${TMP})
+  endif(${ARGC} GREATER 2)
+  find_library(${PREFIX}_LIBRARY
+    NAMES ${${PREFIX}_LIBNAMES}
+    PATHS ${${PREFIX}_PKGCONF_LIBRARY_DIRS}
+  )
+endmacro(libfind_library)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/include/evsql.h	Sun Mar 08 01:33:45 2009 +0200
@@ -0,0 +1,817 @@
+#ifndef EVSQL_H
+#define EVSQL_H
+
+/**
+ * @file src/evsql.h
+ *
+ * A SQL library designed for use with libevent and PostgreSQL's libpq. Provides support for queueing non-transactional
+ * requests, transaction support, parametrized queries and result iteration.
+ *
+ * Currently, the API does not expose the underlying libpq data structures, but since it is currently the only
+ * underlying implementation, there is no guarantee that the same API will actually work with other databases' interface
+ * libraries...
+ *
+ * The order of function calls and callbacks goes something like this:
+ *
+ *  -   evsql_new_pq()
+ *
+ *  -   evsql_trans()
+ *      -   evsql_trans_abort()
+ *      -   evsql_trans_error_cb()
+ *      -   evsql_trans_ready_cb()
+ *
+ *  -   evsql_query(), \ref evsql_param_ + evsql_query_params(), evsql_query_exec()
+ *      -   evsql_query_abort()
+ *      -   evsql_query_cb()
+ *          -   \ref evsql_result_
+ *          -   evsql_result_free()
+ *
+ *  -   evsql_trans_commit()
+ *      -   evsql_trans_done_cb()
+ *
+ */
+
+/**
+ * System includes
+ */
+#include <stdint.h>
+#include <stdbool.h>
+#include <event2/event.h>
+
+/**
+ * XXX: err_t
+ */
+#ifndef LIB_ERR_H
+#define LIB_ERR_H
+#include <errno.h>
+typedef unsigned int err_t;
+#endif
+
+/**
+ * @struct evsql
+ *
+ * The generic session handle used to manage a single "database connector" with multiple queries/transactions.
+ *
+ * @see \ref evsql_
+ */
+struct evsql;
+
+/**
+ * @struct evsql_trans
+ *
+ * Opaque transaction handle returned by evsql_trans() and used for the \ref evsql_query_ functions
+ *
+ * @see \ref evsql_trans_
+ */
+struct evsql_trans;
+
+/**
+ * @struct evsql_query
+ *
+ * Opaque query handle returned by the \ref evsql_query_ functions and used for evsql_query_abort()
+ *
+ * @see \ref evsql_query_
+ */
+struct evsql_query;
+
+/**
+ * @struct evsql_result
+ *
+ * Opaque result handle received by evsql_query_cb(), and used with the \ref evsql_result_ functions
+ *
+ * @see evsql_query_cb
+ * @see \ref evsql_result_
+ */
+struct evsql_result;
+
+/**
+ * Various transaction isolation levels for conveniance
+ *
+ * @see evsql_trans
+ */
+enum evsql_trans_type {
+    EVSQL_TRANS_DEFAULT,
+    EVSQL_TRANS_SERIALIZABLE,
+    EVSQL_TRANS_REPEATABLE_READ,
+    EVSQL_TRANS_READ_COMMITTED,
+    EVSQL_TRANS_READ_UNCOMMITTED,
+};
+
+/**
+ * An item can be in different formats, the classical text-based format (i.e. snprintf "1234") or a more low-level
+ * binary format (i.e uint16_t 0x04F9 in network-byte order).
+ */
+enum evsql_item_format {
+    /** Format values as text strings */
+    EVSQL_FMT_TEXT,
+
+    /** Type-specific binary encoding */
+    EVSQL_FMT_BINARY,
+};
+
+/**
+ * An item has a specific type, these correspond somewhat to the native database types.
+ */
+enum evsql_item_type {
+    /** End marker, zero */
+    EVSQL_TYPE_INVALID,
+    
+    /** A SQL NULL */
+    EVSQL_TYPE_NULL_,
+    
+    /** A `struct evsql_item_binary` */
+    EVSQL_TYPE_BINARY,
+
+    /** A NUL-terminated char* */
+    EVSQL_TYPE_STRING,
+
+    /** A uint16_t value */
+    EVSQL_TYPE_UINT16,
+
+    /** A uint32_t value */
+    EVSQL_TYPE_UINT32,
+
+    /** A uint64_t value */
+    EVSQL_TYPE_UINT64,
+
+    EVSQL_TYPE_MAX
+};
+
+/**
+ * Value for use with EVSQL_TYPE_BINARY, this just a non-NUL-terminated char* and an explicit length
+ */
+struct evsql_item_binary {
+    /** The binary data */
+    const char *ptr;
+
+    /** Number of bytes pointed to by ptr */
+    size_t len;
+};
+
+/**
+ * Metadata about the format and type of an item, this does not hold any actual value.
+ */
+struct evsql_item_info {
+    /** The format */
+    enum evsql_item_format format;
+    
+    /** The type type */
+    enum evsql_item_type type;
+
+    /** Various flags */
+    struct evsql_item_flags {
+        /** The value may be NULL @see evsql_result_next */
+        bool null_ok : 1;
+    } flags;
+};
+
+/**
+ * An union to provide storage for the values of small types
+ *
+ * @see evsql_item
+ */
+union evsql_item_value {
+    /** 16-bit unsigned integer */
+    uint16_t uint16;
+
+    /** 32-bit unsigned integer */
+    uint32_t uint32;
+
+    /** 64-bit unsigned integer */
+    uint64_t uint64;
+};
+
+/**
+ * A generic structure containing the type and value of a query parameter or a result field.
+ *
+ * @see evsql_query_info
+ * @see evsql_query_params
+ * @see evsql_result_info
+ */
+struct evsql_item {
+    /** The "header" containing the type and format */
+    struct evsql_item_info info;
+
+    /**
+     * Pointer to the raw databytes. 
+     * Set to NULL for SQL NULLs, otherwise &value or an external buf 
+     */
+    const char *bytes;
+
+    /**
+     * Size of the byte array pointed to by bytes, zero for EVSQL_FMT_TEXT data.
+     */
+    size_t length;
+
+    /**
+     * Inline storage for small values
+     */
+    union evsql_item_value value;
+    
+    /** Internal flags */
+    struct {
+        /**
+         * The item has a value stored in `value`
+         */
+        bool has_value : 1;
+    } flags;
+};
+
+/**
+ * Query meta-info, similar to a prepared statement.
+ *
+ * Contains the literal SQL query and the types of the parameters, but no more.
+ *
+ * @see evsql_query_exec
+ */
+struct evsql_query_info {
+    /** The SQL query itself */
+    const char *sql;
+
+    /** 
+     * A variable-length array of the item_info parameters, terminated by an EVSQL_TYPE_INVALID entry.
+     */
+    struct evsql_item_info params[];
+};
+
+/**
+ * Contains the query parameter types and their actual values
+ *
+ * @see evsql_query_params
+ */
+struct evsql_query_params {
+    /** Requested result format for this query. XXX: move elsewhere */
+    enum evsql_item_format result_format;
+    
+    /**
+     * A variable-length array of the item parameter-values, terminated by an EVSQL_TYPE_INVALID entry.
+     */
+    struct evsql_item list[];
+};
+
+/**
+ * Result layout metadata. This contains the stucture needed to decode result rows.
+ *
+ * @see evsql_result_begin
+ */
+struct evsql_result_info {
+    /** XXX: make up something useful to stick here */
+    int _unused;
+
+    /**
+     * A variable-length array of the item_info column types.
+     */
+    struct evsql_item_info columns[];
+};
+
+/**
+ * Magic macros for defining param/result info -lists
+ *  
+ * @code
+ *  static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) {
+ *      EVSQL_PARAM( UINT32 ),
+ *      ...,
+ *
+ *      EVSQL_PARAMS_END
+ *  };
+ * @endcode
+ *
+ * @name EVSQL_TYPE/PARAM_*
+ * @{
+ */
+
+/**
+ * A `struct evsql_item_info` initializer, using FMT_BINARY and the given EVSQL_TYPE_ -suffix.
+ *
+ * @param typenam the suffix of an evsql_item_type name
+ *
+ * @see struct evsql_item_info
+ * @see enum evsql_item_type
+ */
+#define EVSQL_TYPE(typenam)                     { EVSQL_FMT_BINARY, EVSQL_TYPE_ ## typenam  }
+
+/**
+ * End marker for a `struct evsql_item_info` array.
+ *
+ * @see struct evsql_item_info
+ */
+#define EVSQL_TYPE_END                          { EVSQL_FMT_BINARY, EVSQL_TYPE_INVALID      }
+
+/**
+ * Initializer block for an evsql_query_params struct
+ */
+#define EVSQL_PARAMS(result_fmt)            { result_fmt, 
+
+/**
+ * An evsql_item initializer
+ */
+#define EVSQL_PARAM(typenam)                    { EVSQL_TYPE(typenam) }
+
+/**
+ * Include the ending item and terminate the pseudo-block started using #EVSQL_PARAMS
+ */
+#define EVSQL_PARAMS_END                        { EVSQL_TYPE_END } \
+                                              } // <<<
+
+// @}
+
+/**
+ * Callback definitions
+ *
+ * @name evsql_*_cb
+ * @{
+ */
+
+/**
+ * Callback for handling query results.
+ *
+ * The query has completed, either succesfully or unsuccesfully.
+ *
+ * Use the \ref evsql_result_ functions to manipulate the results, and call evsql_result_free() (or equivalent) once done.
+ *
+ * @param res The result handle that must be result_free'd after use
+ * @param arg The void* passed to \ref evsql_query_
+ *
+ * @see evsql_query
+ */
+typedef void (*evsql_query_cb)(struct evsql_result *res, void *arg);
+
+/**
+ * Callback for handling global-level errors.
+ *
+ * The evsql is not useable anymore.
+ *
+ * XXX: this is not actually called yet, as no retry logic is implemented, so an evsql itself never fails.
+ *
+ * @see evsql_new_pq
+ */
+typedef void (*evsql_error_cb)(struct evsql *evsql, void *arg);
+
+/**
+ * Callback for handling transaction-level errors. This may be called at any time during a transaction's lifetime,
+ * including from within the \ref evsql_query_ functions (but not always).
+ *
+ * The transaction is not useable anymore.
+ *
+ * @param trans the transaction in question
+ * @param arg the void* passed to evsql_trans
+ *
+ * @see evsql_trans
+ */
+typedef void (*evsql_trans_error_cb)(struct evsql_trans *trans, void *arg);
+
+/**
+ * Callback for handling evsql_trans/evsql_query_abort completion. The transaction is ready for use with \ref evsql_query_.
+ *
+ * @param trans the transaction in question
+ * @param arg the void* passed to evsql_trans
+ *
+ * @see evsql_trans
+ * @see evsql_query_abort
+ */
+typedef void (*evsql_trans_ready_cb)(struct evsql_trans *trans, void *arg);
+
+/**
+ * Callback for handling evsql_trans_commit completion. The transaction was commited, and should not be used anymore.
+ *
+ * @param trans the transaction in question
+ * @param arg the void* passed to evsql_trans
+ *
+ * @see evsql_trans
+ * @see evsql_trans_commit
+ */
+typedef void (*evsql_trans_done_cb)(struct evsql_trans *trans, void *arg);
+
+// @}
+
+/**
+ * Session functions
+ *
+ * @defgroup evsql_* Session interface
+ * @see evsql.h
+ * @{
+ */
+
+/**
+ * Session creation functions
+ *
+ * @defgroup evsql_new_* Session creation interface
+ * @see evsql.h
+ * @{
+ */
+
+/**
+ * Create a new PostgreSQL/libpq (evpq) -based evsql using the given conninfo.
+ *
+ * The given \a pq_conninfo pointer must stay valid for the duration of the evsql's lifetime.
+ *
+ * See the libpq reference manual for the syntax of pq_conninfo
+ *
+ * @param ev_base the libevent base to use
+ * @param pq_conninfo the libpq connection information
+ * @param error_fn XXX: not used, may be NULL
+ * @param cb_arg: XXX: not used, argument for error_fn
+ * @return the evsql context handle for use with other functions
+ */
+struct evsql *evsql_new_pq (struct event_base *ev_base, const char *pq_conninfo, 
+    evsql_error_cb error_fn, 
+    void *cb_arg
+);
+
+// @}
+
+/**
+ * Close a connection. Callbacks for waiting queries will not be run.
+ *
+ * XXX: not implemented yet.
+ *
+ * @param evsql the context handle from \ref evsql_new_
+ */
+void evsql_close (struct evsql *evsql);
+
+// @}
+
+/**
+ * Query API
+ *
+ * @defgroup evsql_query_* Query interface
+ * @see evsql.h
+ * @{
+ */
+
+/**
+ * Queue the given query for execution.
+ *
+ * If \a trans is given (i.e. not NULL), then the transaction must be idle, and the query will be executed in that
+ * transaction's context. Otherwise, the query will be executed without a transaction using an idle connection, or
+ * enqueued for later execution.
+ *
+ * Once the query is complete (got a result, got an error, the connection failed), then \a query_fn will be called.
+ * The callback can use the \ref evsql_result_ functions to manipulate the query results.
+ *
+ * The returned evsql_query handle can be passed to evsql_query_abort at any point before \a query_fn being called. 
+ *
+ * @param evsql the context handle from \ref evsql_new_
+ * @param trans the optional transaction handle from evsql_trans
+ * @param command the raw SQL command itself
+ * @param query_fn the evsql_query_cb() to call once the query is complete
+ * @param cb_arg the void* passed to the above
+ * @return the evsql_query handle that can be used to abort the query
+ */
+struct evsql_query *evsql_query (struct evsql *evsql, struct evsql_trans *trans, const char *command, evsql_query_cb query_fn, void *cb_arg);
+
+/**
+ * Execute the given SQL query using the list of parameter types/values given via evsql_query_params.
+ *
+ * Use the EVSQL_PARAMS macros to declare \a params, and the \ref evsql_param_ functions to populate the values.
+ *
+ * See evsql_query() for more info about behaviour.
+ *
+ * See the <a href="http://www.postgresql.org/docs/8.3/static/libpq-exec.html#LIBPQ-EXEC-MAIN">libpq PQexecParams tip</a>
+ * for the parameter syntax to use.
+ *
+ * @param evsql the context handle from \ref evsql_new_
+ * @param trans the optional transaction handle from evsql_trans
+ * @param command the SQL command to bind the parameters to
+ * @param params the parameter types and values
+ * @param query_fn the evsql_query_cb() to call once the query is complete
+ * @param cb_arg the void* passed to the above
+ * @see evsql_query
+ */
+struct evsql_query *evsql_query_params (struct evsql *evsql, struct evsql_trans *trans, 
+    const char *command, const struct evsql_query_params *params, 
+    evsql_query_cb query_fn, void *cb_arg
+);
+
+/**
+ * Execute the given \a query_info's SQL query with the values given as variable arguments, using the \a query_info to
+ * resolve the types.
+ *
+ * See evsql_query() for more info about behaviour.
+ *
+ * @param evsql the context handle from \ref evsql_new_
+ * @param trans the optional transaction handle from evsql_trans
+ * @param query_info the SQL query information
+ * @param query_fn the evsql_query_cb() to call once the query is complete
+ * @param cb_arg the void* passed to the above
+ * @see evsql_query
+ */
+struct evsql_query *evsql_query_exec (struct evsql *evsql, struct evsql_trans *trans, 
+    const struct evsql_query_info *query_info,
+    evsql_query_cb query_fn, void *cb_arg,
+    ...
+);
+
+/**
+ * Abort a \a query returned by \ref evsql_query_ that has not yet completed (query_fn has not been called yet).
+ *
+ * The actual query itself may or may not be aborted (and hence may or may not be executed on the server), but \a query_fn
+ * will not be called anymore, and the query will dispose of itself and any results returned.
+ *
+ * If the \a query is part of a transaction, then \a trans must be given, and the query must be the query that is currently
+ * executing on that trans. The transaction's \a ready_fn will be called once the query has been aborted and the
+ * transaction is now idle again.
+ *
+ * @param trans if the query is part of a transaction, then it MUST be given here
+ * @param query the in-progress query to abort
+ */
+void evsql_query_abort (struct evsql_trans *trans, struct evsql_query *query);
+
+/**
+ * Print out a textual dump of the given \a sql query and \a params using DEBUG
+ *
+ * @param sql the SQL query command
+ * @param params the list of parameter types and values
+ */
+void evsql_query_debug (const char *sql, const struct evsql_query_params *params);
+
+// @}
+
+/**
+ * Transaction API
+ *
+ * @defgroup evsql_trans_* Transaction interface
+ * @see evsql.h
+ * @{
+ */
+
+/**
+ * Create a new transaction.
+ *
+ * A transaction will be allocated its own connection, and the "BEGIN TRANSACTION ..." query will be sent (use the
+ * \a type argument to specify this). 
+ *
+ * Once the transaction has been opened, the given \a ready_fn will be triggered, and the transaction can then
+ * be used (see \ref evsql_query_).
+ *
+ * If, at any point, the transaction-connection fails, any pending query will be forgotten (i.e. the query callback
+ * will NOT be called), and the given \a error_fn will be called. Note that this includes some, but not all,
+ * cases where \ref evsql_query_ returns an error.
+ *
+ * Once you are done with the transaction, call either evsql_trans_commit() or evsql_trans_abort().
+ *
+ * @param evsql the context handle from \ref evsql_new_
+ * @param type the type of transaction to create
+ * @param error_fn the evsql_trans_error_cb() to call if this transaction fails
+ * @param ready_fn the evsql_trans_ready_cb() to call once this transaction is ready for use
+ * @param done_fn the evsql_trans_done_cb() to call once this transaction has been commmited
+ * @param cb_arg the void* to pass to the above
+ * @return the evsql_trans handle for use with other functions
+ */
+struct evsql_trans *evsql_trans (struct evsql *evsql, enum evsql_trans_type type, 
+    evsql_trans_error_cb error_fn, 
+    evsql_trans_ready_cb ready_fn, 
+    evsql_trans_done_cb done_fn, 
+    void *cb_arg
+);
+
+/**
+ * Commit a transaction using "COMMIT TRANSACTION".
+ *
+ * The transaction must be idle, just like for evsql_query. Once the transaction has been commited, the transaction's
+ * \a done_fn will be called, after which the transaction must not be used anymore.
+ *
+ * You cannot abort a COMMIT, calling trans_abort() on trans after a succesful trans_commit is an error.
+ *
+ * Note that \a done_fn will never be called directly, always indirectly via the event loop.
+ *
+ * @param trans the transaction handle from evsql_trans to commit
+ * @see evsql_trans
+ */
+int evsql_trans_commit (struct evsql_trans *trans);
+
+/**
+ * Abort a transaction, using "ROLLBACK TRANSACTION".
+ *
+ * No more transaction callbacks will be called. If there was a query running, it will be aborted, and the transaction
+ * then rollback'd.
+ *
+ * You cannot abort a COMMIT, calling trans_abort on \a trans after a call to trans_commit is an error.
+ * 
+ * Do not call evsql_trans_abort from within evsql_trans_error_cb()!
+ *
+ * @param trans the transaction from evsql_trans to abort
+ * @see evsql_trans
+ */
+void evsql_trans_abort (struct evsql_trans *trans);
+
+/** 
+ * Retrieve the transaction-specific error message from the underlying engine.
+ *
+ * Intended to be called from evsql_trans_error_cb()
+ */
+const char *evsql_trans_error (struct evsql_trans *trans);
+
+// @}
+
+/**
+ * Parameter-building functions.
+ *
+ * These manipulate the value of the given parameter index.
+ *
+ * @defgroup evsql_param_* Parameter interface
+ * @see evsql.h
+ * @{
+ */
+
+/**
+ * Sets the value of the parameter at the given index
+ *
+ * @param params the evsql_query_params struct
+ * @param param the parameter index
+ * @param ptr pointer to the binary data
+ * @param len size of the binary data in bytes
+ * @return zero on success, <0 on error
+ */
+int evsql_param_binary (struct evsql_query_params *params, size_t param, const char *ptr, size_t len);
+
+/** @see evsql_param_binary */
+int evsql_param_string (struct evsql_query_params *params, size_t param, const char *ptr);
+
+/** @see evsql_param_binary */
+int evsql_param_uint16 (struct evsql_query_params *params, size_t param, uint16_t uval);
+
+/** @see evsql_param_binary */
+int evsql_param_uint32 (struct evsql_query_params *params, size_t param, uint32_t uval);
+
+/**
+ * Sets the given parameter to NULL
+ *
+ * @param params the evsql_query_params struct
+ * @param param the parameter index
+ * @return zero on success, <0 on error
+ */
+int evsql_param_null (struct evsql_query_params *params, size_t param);
+
+/**
+ * Clears all the parameter values (sets them to NULL)
+ *
+ * @param params the evsql_query_params struct
+ * @return zero on success, <0 on error
+ */
+int evsql_params_clear (struct evsql_query_params *params);
+
+// @}
+
+/**
+ * Result-handling functions
+ *
+ * @defgroup evsql_result_* Result interface
+ * @see evsql.h
+ * @see evsql_result
+ * @{
+ */
+
+/**
+ * Check the result for errors. Intended for use with non-data queries, i.e. CREATE, etc.
+ *
+ * Returns zero if the query was OK, err otherwise. EIO indicates an SQL error, the error message can be retrived
+ * using evsql_result_error.
+ *
+ * @param res the result handle passed to evsql_query_cb()
+ * @return zero on success, EIO on SQL error, positive error code otherwise
+ */
+err_t evsql_result_check (struct evsql_result *res);
+
+/**
+ * The iterator-based interface results interface.
+ *
+ * Define an evsql_result_info struct that describes the columns returned by the query, and call evsql_result_begin on
+ * the evsql_result. This verifies the query result, and then prepares it for iteration using evsql_result_next.
+ *
+ * Call evsql_result_end once you've stopped iteration.
+ *
+ * Returns zero if the evsql_result is ready for iteration, err otherwise. EIO indicates an SQL error, the error
+ * message can be retreived using evsql_result_error. The result must be released in both cases.
+ *
+ * Note: currently the iterator state is simply stored in evsql_result, so only one iterator at a time per evsql_result.
+ *
+ * @param info the metadata to use to handle the result row columns
+ * @param res the result handle passed to evsql_query_cb()
+ * @return zero on success, +err on error
+ */
+err_t evsql_result_begin (struct evsql_result_info *info, struct evsql_result *res);
+
+/**
+ * Reads the next result row from the result prepared using evsql_result_begin. Stores the field values into to given
+ * pointer arguments based on the evsql_result_info given to evsql_result_begin.
+ *
+ * If a field is NULL, and the result_info's evsql_item_type has flags.null_ok set, the given pointer is left
+ * untouched, otherwise, an error is returned.
+ *
+ * @param res the result handle previous prepared using evsql_result_begin
+ * @param ... a set of pointers corresponding to the evsql_result_info specified using evsql_result_begin
+ * @return >0 when a row was read, zero when there are no more rows left, and -err on error
+ */
+int evsql_result_next (struct evsql_result *res, ...);
+
+/**
+ * Ends the result iteration, releasing any associated resources and the result itself.
+ *
+ * The result should not be iterated or accessed anymore.
+ *
+ * Note: this does the same thing as evsql_result_free, and works regardless of evsql_result_begin returning
+ * succesfully or not.
+ *
+ * @param res the result handle passed to evsql_query_cb()
+ * @see evsql_result_free
+ */
+void evsql_result_end (struct evsql_result *res);
+
+/**
+ * Get the error message associated with the result, intended for use after evsql_result_check/begin return an error
+ * code.
+ * 
+ * @param res the result handle passed to evsql_query_cb()
+ * @return a char* containing the NUL-terminated error string. Valid until evsql_result_free is called.
+ */
+const char *evsql_result_error (const struct evsql_result *res);
+
+/**
+ * Get the number of data rows returned by the query
+ *
+ * @param res the result handle passed to evsql_query_cb()
+ * @return the number of rows, >= 0
+ */
+size_t evsql_result_rows (const struct evsql_result *res);
+
+/**
+ * Get the number of columns in the data results from the query
+ *
+ * @param res the result handle passed to evsql_query_cb()
+ * @return the number of columns, presumeably zero if there were no results
+ */
+size_t evsql_result_cols (const struct evsql_result *res);
+
+/**
+ * Get the number of rows affected by an UPDATE/INSERT/etc query.
+ *
+ * @param res the result handle passed to evsql_query_cb()
+ * @return the number of rows affected, >= 0
+ */
+size_t evsql_result_affected (const struct evsql_result *res);
+
+/**
+ * Fetch the raw binary value for the given field, returning it via ptr/size.
+ *
+ * The given row/col must be within bounds as returned by evsql_result_rows/cols.
+ *
+ * *ptr will point to *size bytes of read-only memory allocated internally.
+ *
+ * @param res the result handle passed to evsql_query_cb()
+ * @param row the row index to access
+ * @param col the column index to access
+ * @param ptr where to store a pointer to the read-only field data, free'd upon evsql_result_free
+ * @param size updated to the size of the field value pointed to by ptr
+ * @param nullok when true and the field value is NULL, *ptr and *size are not modified, otherwise NULL means an error
+ * @return zero on success, <0 on error
+ */
+int evsql_result_binary (const struct evsql_result *res, size_t row, size_t col, const char **ptr, size_t *size, bool nullok);
+
+/**
+ * Fetch the textual value of the given field, returning it via ptr.
+ *
+ * The given row/col must be within bounds as returned by evsql_result_rows/cols.
+ *
+ * *ptr will point to a NUL-terminated string allocated internally.
+ *
+ * @param res the result handle passed to evsql_query_cb()
+ * @param row the row index to access
+ * @param col the column index to access
+ * @param ptr where to store a pointer to the read-only field data, free'd upon evsql_result_free
+ * @param nullok when true and the field value is NULL, *ptr and *size are not modified, otherwise NULL means an error
+ * @return zero on success, <0 on error
+ */
+int evsql_result_string (const struct evsql_result *res, size_t row, size_t col, const char **ptr, int nullok);
+
+/**
+ * Use evsql_result_binary to read a binary field value, and then convert it using ntoh[slq], storing the value in
+ * *val.
+ *
+ * The given row/col must be within bounds as returned by evsql_result_rows/cols.
+ *
+ * @param res the result handle passed to evsql_query_cb()
+ * @param row the row index to access
+ * @param col the column index to access
+ * @param uval where to store the decoded value
+ * @param nullok when true and the field value is NULL, *ptr and *size are not modified, otherwise NULL means an error
+ * @return zero on success, <0 on error
+ */
+int evsql_result_uint16 (const struct evsql_result *res, size_t row, size_t col, uint16_t *uval, int nullok);
+
+/** @see evsql_result_uint16 */
+int evsql_result_uint32 (const struct evsql_result *res, size_t row, size_t col, uint32_t *uval, int nullok);
+
+/** @see evsql_result_uint16 */
+int evsql_result_uint64 (const struct evsql_result *res, size_t row, size_t col, uint64_t *uval, int nullok);
+
+/**
+ * Every result handle passed to evsql_query_cb() MUST be released by the user, using this function.
+ *
+ * @param res the result handle passed to evsql_query_cb()
+ */
+void evsql_result_free (struct evsql_result *res);
+
+// @}
+
+#endif /* EVSQL_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/CMakeLists.txt	Sun Mar 08 01:33:45 2009 +0200
@@ -0,0 +1,15 @@
+# add our include path
+include_directories (${evsql_SOURCE_DIR}/include ${LibEvent_INCLUDE_DIRS})
+
+# define our source code modules
+set (LIB_SOURCES "lib/log.c")
+set (EVPQ_SOURCES evpq.c)
+set (EVSQL_SOURCES core.c util.c)
+
+# XXX: silly cmake does silly things when you SET with only one arg
+set (EVSQL_SOURCES lib/log.c evpq.c core.c query.c result.c util.c)
+
+# add our library
+add_library (evsql STATIC ${EVSQL_SOURCES})
+target_link_libraries (evsql ${LibEvent_LIBRARIES})
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/core.c	Sun Mar 08 01:33:45 2009 +0200
@@ -0,0 +1,915 @@
+#define _GNU_SOURCE
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+
+#include "internal.h"
+#include "lib/log.h"
+#include "lib/error.h"
+#include "lib/misc.h"
+
+/*
+ * A couple function prototypes
+ */ 
+static void _evsql_pump (struct evsql *evsql, struct evsql_conn *conn);
+
+/*
+ * Actually execute the given query.
+ *
+ * The backend should be able to accept the query at this time.
+ *
+ * You should assume that if trying to execute a query fails, then the connection should also be considred as failed.
+ */
+static int _evsql_query_exec (struct evsql_conn *conn, struct evsql_query *query, const char *command) {
+    int err;
+
+    DEBUG("evsql.%p: exec query=%p on trans=%p on conn=%p:", conn->evsql, query, conn->trans, conn);
+
+    switch (conn->evsql->type) {
+        case EVSQL_EVPQ:
+            // got params?
+            if (query->params.count) {
+                err = evpq_query_params(conn->engine.evpq, command,
+                    query->params.count, 
+                    query->params.types, 
+                    query->params.values, 
+                    query->params.lengths, 
+                    query->params.formats, 
+                    query->params.result_format
+                );
+
+            } else {
+                // plain 'ole query
+                err = evpq_query(conn->engine.evpq, command);
+            }
+
+            if (err) {
+                if (PQstatus(evpq_pgconn(conn->engine.evpq)) != CONNECTION_OK)
+                    WARNING("conn failed");
+                else
+                    WARNING("query failed, dropping conn as well");
+            }
+
+            break;
+        
+        default:
+            FATAL("evsql->type");
+    }
+
+    if (!err)
+        // assign the query
+        conn->query = query;
+
+    return err;
+}
+
+void _evsql_query_free (struct evsql_query *query) {
+    if (!query)
+        return;
+        
+    assert(query->command == NULL);
+    
+    // free params if present
+    free(query->params.types);
+    free(query->params.values);
+    free(query->params.lengths);
+    free(query->params.formats);
+
+    // free the query itself
+    free(query);
+}
+
+/*
+ * Execute the callback if res is given, and free the query.
+ *
+ * The query has been aborted, it will simply be freed
+ */
+static void _evsql_query_done (struct evsql_query *query, struct evsql_result *res) {
+    if (res) {
+        if (query->cb_fn)
+            // call the callback
+            query->cb_fn(res, query->cb_arg);
+        else {
+            WARNING("supressing cb_fn because query was aborted");
+            
+            // free the results
+            evsql_result_free(res);
+        }
+    }
+
+    // free
+    _evsql_query_free(query);
+}
+
+/*
+ * XXX:
+ * /
+static void _evsql_destroy (struct evsql *evsql, const struct evsql_result *res) {
+    struct evsql_query *query;
+    
+    // clear the queue
+    while ((query = TAILQ_FIRST(&evsql->query_queue)) != NULL) {
+        _evsql_query_done(query, res);
+        
+        TAILQ_REMOVE(&evsql->query_queue, query, entry);
+    }
+    
+    // free
+    free(evsql);
+}
+*/
+
+/*
+ * Free the transaction, it should already be deassociated from the query and conn.
+ */
+static void _evsql_trans_free (struct evsql_trans *trans) {
+    // ensure we don't leak anything
+    assert(trans->query == NULL);
+    assert(trans->conn == NULL);
+    
+    // free
+    free(trans);
+}
+
+/*
+ * Release a connection. It should already be deassociated from the trans and query.
+ *
+ * Releases the engine, removes from the conn_list and frees this.
+ */
+static void _evsql_conn_release (struct evsql_conn *conn) {
+    // ensure we don't leak anything
+    assert(conn->trans == NULL);
+    assert(conn->query == NULL);
+
+    // release the engine
+    switch (conn->evsql->type) {
+        case EVSQL_EVPQ:
+            evpq_release(conn->engine.evpq);
+            break;
+        
+        default:
+            FATAL("evsql->type");
+    }
+    
+    // remove from list
+    LIST_REMOVE(conn, entry);
+    
+    // catch deadlocks
+    assert(!LIST_EMPTY(&conn->evsql->conn_list) || TAILQ_EMPTY(&conn->evsql->query_queue));
+
+    // free
+    free(conn);
+}
+
+/*
+ * Release a transaction, it should already be deassociated from the query.
+ *
+ * Perform a two-way-deassociation with the conn, and then free the trans.
+ */
+static void _evsql_trans_release (struct evsql_trans *trans) {
+    assert(trans->query == NULL);
+    assert(trans->conn != NULL);
+
+    // deassociate the conn
+    trans->conn->trans = NULL; trans->conn = NULL;
+
+    // free the trans
+    _evsql_trans_free(trans);
+}
+
+/*
+ * Fail a single query, this will trigger the callback and free it.
+ *
+ * NOTE: Only for *TRANSACTIONLESS* queries.
+ */
+static void _evsql_query_fail (struct evsql* evsql, struct evsql_query *query) {
+    struct evsql_result res; ZINIT(res);
+    
+    // set up the result_info
+    res.evsql = evsql;
+    res.error = 1;
+    
+    // finish off the query
+    _evsql_query_done(query, &res);
+}
+
+/*
+ * Fail a transaction, this will silently drop any query, trigger the error callback, two-way-deassociate/release the
+ * conn, and then free the trans.
+ */ 
+static void _evsql_trans_fail (struct evsql_trans *trans) {
+    if (trans->query) {
+        // free the query silently
+        _evsql_query_free(trans->query); trans->query = NULL;
+
+        // also deassociate it from the conn!
+        trans->conn->query = NULL;
+    }
+
+    // tell the user
+    // XXX: trans is in a bad state during this call
+    if (trans->error_fn)
+        trans->error_fn(trans, trans->cb_arg);
+    else
+        WARNING("supressing error because error_fn was NULL");
+ 
+    // deassociate and release the conn
+    trans->conn->trans = NULL; _evsql_conn_release(trans->conn); trans->conn = NULL;
+
+    // pump the queue for requests that were waiting for this connection
+    _evsql_pump(trans->evsql, NULL);
+
+    // free the trans
+    _evsql_trans_free(trans);
+}
+
+/*
+ * Fail a connection. If the connection is transactional, this will just call _evsql_trans_fail, but otherwise it will
+ * fail any ongoing query, and then release the connection.
+ */
+static void _evsql_conn_fail (struct evsql_conn *conn) {
+    if (conn->trans) {
+        // let transactions handle their connection failures
+        _evsql_trans_fail(conn->trans);
+
+    } else {
+        if (conn->query) {
+            // fail the in-progress query
+            _evsql_query_fail(conn->evsql, conn->query); conn->query = NULL;
+        }
+
+        // finish off the whole connection
+        _evsql_conn_release(conn);
+    }
+}
+
+/*
+ * Processes enqueued non-transactional queries until the queue is empty, or we managed to exec a query.
+ *
+ * If execing a query on a connection fails, both the query and the connection are failed (in that order).
+ *
+ * Any further queries will then also be failed, because there's no reconnection/retry logic yet.
+ *
+ * This means that if conn is NULL, all queries are failed.
+ */
+static void _evsql_pump (struct evsql *evsql, struct evsql_conn *conn) {
+    struct evsql_query *query;
+    int err;
+    
+    // look for waiting queries
+    while ((query = TAILQ_FIRST(&evsql->query_queue)) != NULL) {
+        // zero err
+        err = 0;
+
+        // dequeue
+        TAILQ_REMOVE(&evsql->query_queue, query, entry);
+        
+        if (conn) {
+            // try and execute it
+            err = _evsql_query_exec(conn, query, query->command);
+        }
+
+        // free the command buf
+        free(query->command); query->command = NULL;
+
+        if (err || !conn) {
+            if (!conn) {
+                // warn when dropping queries
+                WARNING("failing query becuse there are no conns");
+            }
+
+            // fail the query
+            _evsql_query_fail(evsql, query);
+            
+            if (conn) {
+                // fail the connection
+                WARNING("failing the connection because a query-exec failed");
+
+                _evsql_conn_fail(conn); conn = NULL;
+            }
+
+        } else {
+            // we have succesfully enqueued a query, and we can wait for this connection to complete
+            break;
+
+        }
+
+        // handle the rest of the queue
+    }
+    
+    // ok
+    return;
+}
+
+/*
+ * Callback for a trans's 'BEGIN' query, which means the transaction is now ready for use.
+ */
+static void _evsql_trans_ready (struct evsql_result *res, void *arg) {
+    struct evsql_trans *trans = arg;
+
+    assert(trans != NULL);
+
+    // check for errors
+    if (res->error)
+        ERROR("transaction 'BEGIN' failed: %s", evsql_result_error(res));
+    
+    // transaction is now ready for use
+    trans->ready_fn(trans, trans->cb_arg);
+    
+    // good
+    return;
+
+error:
+    _evsql_trans_fail(trans);
+}
+
+/*
+ * The transaction's connection is ready, send the 'BEGIN' query.
+ *
+ * If anything fails, calls _evsql_trans_fail and returns nonzero, zero on success
+ */
+static int _evsql_trans_conn_ready (struct evsql *evsql, struct evsql_trans *trans) {
+    char trans_sql[EVSQL_QUERY_BEGIN_BUF];
+    const char *isolation_level;
+    int ret;
+    
+    // determine the isolation_level to use
+    switch (trans->type) {
+        case EVSQL_TRANS_DEFAULT:
+            isolation_level = NULL; break;
+
+        case EVSQL_TRANS_SERIALIZABLE:
+            isolation_level = "SERIALIZABLE"; break;
+
+        case EVSQL_TRANS_REPEATABLE_READ:
+            isolation_level = "REPEATABLE READ"; break;
+
+        case EVSQL_TRANS_READ_COMMITTED:
+            isolation_level = "READ COMMITTED"; break;
+
+        case EVSQL_TRANS_READ_UNCOMMITTED:
+            isolation_level = "READ UNCOMMITTED"; break;
+
+        default:
+            FATAL("trans->type: %d", trans->type);
+    }
+    
+    // build the trans_sql
+    if (isolation_level)
+        ret = snprintf(trans_sql, EVSQL_QUERY_BEGIN_BUF, "BEGIN TRANSACTION ISOLATION LEVEL %s", isolation_level);
+    else
+        ret = snprintf(trans_sql, EVSQL_QUERY_BEGIN_BUF, "BEGIN TRANSACTION");
+    
+    // make sure it wasn't truncated
+    if (ret >= EVSQL_QUERY_BEGIN_BUF)
+        ERROR("trans_sql overflow: %d >= %d", ret, EVSQL_QUERY_BEGIN_BUF);
+    
+    // execute the query
+    if (evsql_query(evsql, trans, trans_sql, _evsql_trans_ready, trans) == NULL)
+        ERROR("evsql_query");
+    
+    // success
+    return 0;
+
+error:
+    // fail the transaction
+    _evsql_trans_fail(trans);
+
+    return -1;
+}
+
+/*
+ * The evpq connection was succesfully established.
+ */ 
+static void _evsql_evpq_connected (struct evpq_conn *_conn, void *arg) {
+    struct evsql_conn *conn = arg;
+
+    if (conn->trans)
+        // notify the transaction
+        // don't care about errors
+        (void) _evsql_trans_conn_ready(conn->evsql, conn->trans);
+    
+    else
+        // pump any waiting transactionless queries
+        _evsql_pump(conn->evsql, conn);
+}
+
+/*
+ * Got one result on this evpq connection.
+ */
+static void _evsql_evpq_result (struct evpq_conn *_conn, PGresult *result, void *arg) {
+    struct evsql_conn *conn = arg;
+    struct evsql_query *query = conn->query;
+
+    assert(query != NULL);
+
+    // if we get multiple results, only return the first one
+    if (query->result.pq) {
+        WARNING("[evsql] evpq query returned multiple results, discarding previous one");
+        
+        PQclear(query->result.pq); query->result.pq = NULL;
+    }
+    
+    // remember the result
+    query->result.pq = result;
+}
+
+/*
+ * No more results for this query.
+ */
+static void _evsql_evpq_done (struct evpq_conn *_conn, void *arg) {
+    struct evsql_conn *conn = arg;
+    struct evsql_query *query = conn->query;
+    struct evsql_result res; ZINIT(res);
+    
+    assert(query != NULL);
+    
+    // set up the result_info
+    res.evsql = conn->evsql;
+    res.result = query->result;
+    
+    if (query->result.pq == NULL) {
+        // if a query didn't return any results (bug?), warn and fail the query
+        WARNING("[evsql] evpq query didn't return any results");
+
+        res.error = 1;
+    
+    } else if (strcmp(PQresultErrorMessage(query->result.pq), "") != 0) {
+        // the query failed with some error
+        res.error = 1;
+
+    } else {
+        // the query succeeded \o/
+        res.error = 0;
+
+    }
+
+    // de-associate the query from the connection
+    conn->query = NULL;
+    
+    // how we handle query completion depends on if we're a transaction or not
+    if (conn->trans) {
+        // we can deassign the trans's query
+        conn->trans->query = NULL;
+
+        // was an abort?
+        if (!query->cb_fn)
+            // notify the user that the transaction query has been aborted
+            conn->trans->ready_fn(conn->trans, conn->trans->cb_arg);
+
+        // then hand the query to the user
+        _evsql_query_done(query, &res);
+        
+    } else {
+        // a transactionless query, so just finish it off and pump any other waiting ones
+        _evsql_query_done(query, &res);
+
+        // pump the next one
+        _evsql_pump(conn->evsql, conn);
+    }
+}
+
+/*
+ * The connection failed.
+ */
+static void _evsql_evpq_failure (struct evpq_conn *_conn, void *arg) {
+    struct evsql_conn *conn = arg;
+    
+    // just fail the conn
+    _evsql_conn_fail(conn);
+}
+
+/*
+ * Our evpq behaviour
+ */
+static struct evpq_callback_info _evsql_evpq_cb_info = {
+    .fn_connected       = _evsql_evpq_connected,
+    .fn_result          = _evsql_evpq_result,
+    .fn_done            = _evsql_evpq_done,
+    .fn_failure         = _evsql_evpq_failure,
+};
+
+/*
+ * Allocate the generic evsql context.
+ */
+static struct evsql *_evsql_new_base (struct event_base *ev_base, evsql_error_cb error_fn, void *cb_arg) {
+    struct evsql *evsql = NULL;
+    
+    // allocate it
+    if ((evsql = calloc(1, sizeof(*evsql))) == NULL)
+        ERROR("calloc");
+
+    // store
+    evsql->ev_base = ev_base;
+    evsql->error_fn = error_fn;
+    evsql->cb_arg = cb_arg;
+
+    // init
+    LIST_INIT(&evsql->conn_list);
+    TAILQ_INIT(&evsql->query_queue);
+
+    // done
+    return evsql;
+
+error:
+    return NULL;
+}
+
+/*
+ * Start a new connection and add it to the list, it won't be ready until _evsql_evpq_connected is called
+ */
+static struct evsql_conn *_evsql_conn_new (struct evsql *evsql) {
+    struct evsql_conn *conn = NULL;
+    
+    // allocate
+    if ((conn = calloc(1, sizeof(*conn))) == NULL)
+        ERROR("calloc");
+
+    // init
+    conn->evsql = evsql;
+    
+    // connect the engine
+    switch (evsql->type) {
+        case EVSQL_EVPQ:
+            if ((conn->engine.evpq = evpq_connect(evsql->ev_base, evsql->engine_conf.evpq, _evsql_evpq_cb_info, conn)) == NULL)
+                goto error;
+            
+            break;
+            
+        default:
+            FATAL("evsql->type");
+    }
+
+    // add it to the list
+    LIST_INSERT_HEAD(&evsql->conn_list, conn, entry);
+
+    // success
+    return conn;
+
+error:
+    free(conn);
+
+    return NULL;
+}
+
+struct evsql *evsql_new_pq (struct event_base *ev_base, const char *pq_conninfo, evsql_error_cb error_fn, void *cb_arg) {
+    struct evsql *evsql = NULL;
+    
+    // base init
+    if ((evsql = _evsql_new_base (ev_base, error_fn, cb_arg)) == NULL)
+        goto error;
+
+    // store conf
+    evsql->engine_conf.evpq = pq_conninfo;
+
+    // pre-create one connection
+    if (_evsql_conn_new(evsql) == NULL)
+        goto error;
+
+    // done
+    return evsql;
+
+error:
+    // XXX: more complicated than this?
+    free(evsql); 
+
+    return NULL;
+}
+
+/*
+ * Checks if the connection is already allocated for some other trans/query.
+ *
+ * Returns:
+ *      0       connection idle, can be allocated
+ *      >1      connection busy
+ */
+static int _evsql_conn_busy (struct evsql_conn *conn) {
+    // transactions get the connection to themselves
+    if (conn->trans)
+        return 1;
+    
+    // if it has a query assigned, it's busy
+    if (conn->query)
+        return 1;
+
+    // otherwise, it's all idle
+    return 0;
+}
+
+/*
+ * Checks if the connection is ready for use (i.e. _evsql_evpq_connected was called).
+ *
+ * The connection should not already have a query running.
+ *
+ * Returns 
+ *  <0  the connection is not valid (failed, query in progress)
+ *  0   the connection is still pending, and will become ready at some point
+ *  >0  it's ready
+ */
+static int _evsql_conn_ready (struct evsql_conn *conn) {
+    switch (conn->evsql->type) {
+        case EVSQL_EVPQ: {
+            enum evpq_state state = evpq_state(conn->engine.evpq);
+            
+            switch (state) {
+                case EVPQ_CONNECT:
+                    return 0;
+                
+                case EVPQ_CONNECTED:
+                    return 1;
+
+                case EVPQ_QUERY:
+                case EVPQ_INIT:
+                case EVPQ_FAILURE:
+                    return -1;
+                
+                default:
+                    FATAL("evpq_state: %d", state);
+            }
+
+        }
+        
+        default:
+            FATAL("evsql->type: %d", conn->evsql->type);
+    }
+}
+
+/*
+ * Allocate a connection for use and return it via *conn_ptr, or if may_queue is nonzero and the connection pool is
+ * getting full, return NULL (query should be queued).
+ *
+ * Note that the returned connection might not be ready for use yet (if we created a new one, see _evsql_conn_ready).
+ *
+ * Returns zero if a connection was found or the request should be queued, or nonzero if something failed and the
+ * request should be dropped.
+ */
+static int _evsql_conn_get (struct evsql *evsql, struct evsql_conn **conn_ptr, int may_queue) {
+    int have_nontrans = 0;
+    *conn_ptr = NULL;
+    
+    // find a connection that isn't busy and is ready (unless the query queue is empty).
+    LIST_FOREACH(*conn_ptr, &evsql->conn_list, entry) {
+        // we can only have a query enqueue itself if there is a non-trans conn it can later use
+        if (!(*conn_ptr)->trans)
+            have_nontrans = 1;
+
+        // skip busy conns always
+        if (_evsql_conn_busy(*conn_ptr))
+            continue;
+        
+        // accept pending conns as long as there are NO enqueued queries (might cause deadlock otherwise)
+        if (_evsql_conn_ready(*conn_ptr) == 0 && TAILQ_EMPTY(&evsql->query_queue))
+            break;
+
+        // accept conns that are in a fully ready state
+        if (_evsql_conn_ready(*conn_ptr) > 0)
+            break;
+    }
+    
+    // if we found an idle connection, we can just return that right away
+    if (*conn_ptr)
+        return 0;
+
+    // return NULL if may_queue and we have a non-trans conn that we can, at some point, use
+    if (may_queue && have_nontrans)
+        return 0;
+    
+    // we need to open a new connection
+    if ((*conn_ptr = _evsql_conn_new(evsql)) == NULL)
+        goto error;
+
+    // good
+    return 0;
+error:
+    return -1;
+}
+
+struct evsql_trans *evsql_trans (struct evsql *evsql, enum evsql_trans_type type, evsql_trans_error_cb error_fn, evsql_trans_ready_cb ready_fn, evsql_trans_done_cb done_fn, void *cb_arg) {
+    struct evsql_trans *trans = NULL;
+
+    // allocate it
+    if ((trans = calloc(1, sizeof(*trans))) == NULL)
+        ERROR("calloc");
+
+    // store
+    trans->evsql = evsql;
+    trans->ready_fn = ready_fn;
+    trans->done_fn = done_fn;
+    trans->cb_arg = cb_arg;
+    trans->type = type;
+
+    // find a connection
+    if (_evsql_conn_get(evsql, &trans->conn, 0))
+        ERROR("_evsql_conn_get");
+
+    // associate the conn
+    trans->conn->trans = trans;
+
+    // is it already ready?
+    if (_evsql_conn_ready(trans->conn) > 0) {
+        // call _evsql_trans_conn_ready directly, it will handle cleanup (silently, !error_fn)
+        if (_evsql_trans_conn_ready(evsql, trans)) {
+            // return NULL directly
+            return NULL;
+        }
+
+    } else {
+        // otherwise, wait for the conn to be ready
+         
+    }
+    
+    // and let it pass errors to the user
+    trans->error_fn = error_fn;
+
+    // ok
+    return trans;
+
+error:
+    free(trans);
+
+    return NULL;
+}
+
+/*
+ * Internal query functions
+ */
+struct evsql_query *_evsql_query_new (struct evsql *evsql, struct evsql_trans *trans, evsql_query_cb query_fn, void *cb_arg) {
+    struct evsql_query *query = NULL;
+    
+    // if it's part of a trans, then make sure the trans is idle
+    if (trans && trans->query)
+        ERROR("transaction is busy");
+
+    // allocate it
+    if ((query = calloc(1, sizeof(*query))) == NULL)
+        ERROR("calloc");
+
+    // store
+    query->cb_fn = query_fn;
+    query->cb_arg = cb_arg;
+
+    // success
+    return query;
+
+error:
+    return NULL;
+}
+
+int _evsql_query_enqueue (struct evsql *evsql, struct evsql_trans *trans, struct evsql_query *query, const char *command) {
+    // transaction queries are handled differently
+    if (trans) {
+        // it's an in-transaction query
+        assert(trans->query == NULL);
+        
+        // assign the query
+        trans->query = query;
+
+        // execute directly
+        if (_evsql_query_exec(trans->conn, query, command)) {
+            // ack, fail the transaction
+            _evsql_trans_fail(trans);
+            
+            // caller frees query
+            goto error;
+        }
+
+    } else {
+        struct evsql_conn *conn;
+        
+        // find an idle connection
+        if ((_evsql_conn_get(evsql, &conn, 1)))
+            ERROR("couldn't allocate a connection for the query");
+
+        // we must enqueue if no idle conn or the conn is not yet ready
+        if (conn && _evsql_conn_ready(conn) > 0) {
+            // execute directly
+            if (_evsql_query_exec(conn, query, command)) {
+                // ack, fail the connection
+                _evsql_conn_fail(conn);
+                
+                // make sure we don't deadlock any queries, but if this query got a conn directly, then we shouldn't
+                // have any queries enqueued anyways
+                assert(TAILQ_EMPTY(&evsql->query_queue));
+                
+                // caller frees query
+                goto error;
+            }
+
+        } else {
+            // copy the command for later execution
+            if ((query->command = strdup(command)) == NULL)
+                ERROR("strdup");
+            
+            // enqueue until some connection pumps the queue
+            TAILQ_INSERT_TAIL(&evsql->query_queue, query, entry);
+        }
+    }
+
+    // ok, good
+    return 0;
+
+error:
+    return -1;
+}
+
+
+void _evsql_trans_commit_res (struct evsql_result *res, void *arg) {
+    struct evsql_trans *trans = arg;
+
+    // check for errors
+    if (res->error)
+        ERROR("transaction 'COMMIT' failed: %s", evsql_result_error(res));
+    
+    // transaction is now done
+    trans->done_fn(trans, trans->cb_arg);
+    
+    // release it
+    _evsql_trans_release(trans);
+
+    // success
+    return;
+
+error:
+    _evsql_trans_fail(trans);
+}
+
+int evsql_trans_commit (struct evsql_trans *trans) {
+    static const char *sql = "COMMIT TRANSACTION";
+
+    if (trans->query)
+        ERROR("cannot COMMIT because transaction is still busy");
+    
+    // query
+    if (evsql_query(trans->evsql, trans, sql, _evsql_trans_commit_res, trans) == NULL)
+        goto error;
+    
+    // mark it as commited in case someone wants to abort it
+    trans->has_commit = 1;
+
+    // success
+    return 0;
+
+error:
+    return -1;
+}
+
+void _evsql_trans_rollback_res (struct evsql_result *res, void *arg) {
+    struct evsql_trans *trans = arg;
+
+    // fail the connection on errors
+    if (res->error)
+        ERROR("transaction 'ROLLBACK' failed: %s", evsql_result_error(res));
+
+    // release it
+    _evsql_trans_release(trans);
+
+    // success
+    return;
+
+error:
+    // fail the connection too, errors are supressed
+    _evsql_trans_fail(trans);
+}
+
+/*
+ * Used as the ready_fn callback in case of abort, otherwise directly
+ */
+void _evsql_trans_rollback (struct evsql_trans *trans, void *arg) {
+    static const char *sql = "ROLLBACK TRANSACTION";
+
+    (void) arg;
+
+    // query
+    if (evsql_query(trans->evsql, trans, sql, _evsql_trans_rollback_res, trans) == NULL) {
+        // fail the transaction/connection, errors are supressed
+        _evsql_trans_fail(trans);
+    }
+
+}
+
+void evsql_trans_abort (struct evsql_trans *trans) {
+    // supress errors
+    trans->error_fn = NULL;
+    
+    if (trans->has_commit) {
+        // abort after commit doesn't make sense
+        FATAL("transaction was already commited");
+    }
+
+    if (trans->query) {
+        // gah, some query is running
+        WARNING("aborting pending query");
+        
+        // prepare to rollback once complete by hijacking ready_fn
+        trans->ready_fn = _evsql_trans_rollback;
+        
+        // abort
+        evsql_query_abort(trans, trans->query);
+
+    } else {
+        // just rollback directly
+        _evsql_trans_rollback(trans, NULL);
+
+    }
+}
+
--- a/src/dbfs.c	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-
-/*
- * A simple PostgreSQL-based filesystem.
- */
-
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-#include <assert.h>
-
-#include <event2/event.h>
-
-#include "dbfs.h"
-#include "evfuse.h"
-#include "evsql.h"
-#include "lib/log.h"
-#include "lib/signals.h"
-#include "lib/misc.h"
-
-#define CONNINFO_DEFAULT "dbname=dbfs port=5433"
-
-int main (int argc, char **argv) {
-    struct event_base *ev_base = NULL;
-    struct signals *signals = NULL;
-    struct dbfs *ctx = NULL;
-    const char *db_conninfo;
-    struct fuse_args fuse_args = FUSE_ARGS_INIT(argc, argv);
-    
-    // parse args, XXX: fuse_args
-    db_conninfo = CONNINFO_DEFAULT;
-    
-    // init libevent
-    if ((ev_base = event_base_new()) == NULL)
-        ERROR("event_base_new");
-    
-    // setup signals
-    if ((signals = signals_default(ev_base)) == NULL)
-        ERROR("signals_default");
-
-    // setup dbfs
-    if ((ctx = dbfs_new(ev_base, &fuse_args, db_conninfo)) == NULL)
-        ERROR("dbfs_new");
-
-    // run libevent
-    INFO("running libevent loop");
-
-    if (event_base_dispatch(ev_base))
-        PERROR("event_base_dispatch");
-    
-    // clean shutdown
-
-error :
-    if (ctx)
-        dbfs_free(ctx);
-    
-    if (signals)
-        signals_free(signals);
-
-    if (ev_base)
-        event_base_free(ev_base);
-    
-    fuse_opt_free_args(&fuse_args);
-}
-
--- a/src/dbfs.h	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,25 +0,0 @@
-#ifndef DBFS_H
-#define DBFS_H
-
-#include "evfuse.h"
-
-/*
- * External interface for dbfs
- */
-
-/*
- * Context struct.
- */
-struct dbfs;
-
-/*
- * Create the evsql and evfuse contexts and run the fs
- */
-struct dbfs *dbfs_new (struct event_base *ev_base, struct fuse_args *args, const char *db_conninfo);
-
-/*
- * Release the dbfs's resources and free it
- */
-void dbfs_free (struct dbfs *ctx);
-
-#endif /* DBFS_H */
--- a/src/dbfs/attr.c	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,185 +0,0 @@
-
-#include "dbfs.h"
-#include "../lib/log.h"
-#include "../lib/misc.h"
-
-// max. size for a setattr UPDATE query
-#define DBFS_SETATTR_SQL_MAX 512
-
-// for building the setattr UPDATE
-#define FIELD(to_set, flag, field, value) ((to_set) & (flag)) ? (field " = " value ", ") : ""
-
-void _dbfs_attr_res (struct evsql_result *res, void *arg) {
-    struct fuse_req *req = arg;
-    struct stat st; ZINIT(st);
-    int err = 0;
-    
-    uint32_t ino;
-    struct dbfs_stat_values stat_values;
-
-    // result info
-    static struct evsql_result_info result_info = {
-        0, {
-            {   EVSQL_FMT_BINARY,   EVSQL_TYPE_UINT32   },  // inodes.ino
-            DBFS_STAT_RESULT_INFO,
-            {   0,                  0                   }
-        }
-    };
-
-    // begin
-    if ((err = evsql_result_begin(&result_info, res)))
-        EERROR(err, "query failed");
-
-    // get the one row of data
-    if ((err = evsql_result_next(res, &ino, DBFS_STAT_RESULT_VALUES(&stat_values))) <= 0)
-        EERROR(err = (err ? err : ENOENT), "evsql_result_next");
-
-    INFO("\t[dbfs.getattr %p] -> ino=%lu, stat follows", req, (unsigned long int) ino);
-
-    // inode
-    st.st_ino = ino;
-
-    // stat attrs
-    if ((err = _dbfs_stat_info(&st, &stat_values)))
-        goto error;
-    
-    // reply
-    if ((err = fuse_reply_attr(req, &st, st.st_nlink ? CACHE_TIMEOUT : 0)))
-        EERROR(err, "fuse_reply_entry");
-
-error:
-    if (err && (err = fuse_reply_err(req, err)))
-        EWARNING(err, "fuse_reply_err");
-
-    // free
-    evsql_result_free(res);
-}
-
-void dbfs_getattr (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi) {
-    struct dbfs *ctx = fuse_req_userdata(req);
-    struct evsql_query *query;
-    int err;
-    
-    (void) fi;
-
-    INFO("[dbfs.getattr %p] ino=%lu", req, ino);
-
-    const char *sql =
-        "SELECT"
-        " inodes.ino, " DBFS_STAT_COLS
-        " FROM inodes"
-        " WHERE inodes.ino = $1::int4";
-
-    static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) {
-        EVSQL_PARAM ( UINT32 ),
-
-        EVSQL_PARAMS_END
-    };
-
-    // build params
-    if (0
-        ||  evsql_param_uint32(&params, 0, ino)
-    )
-        SERROR(err = EIO);
-        
-    // query
-    if ((query = evsql_query_params(ctx->db, NULL, sql, &params, _dbfs_attr_res, req)) == NULL)
-        SERROR(err = EIO);
-
-    // handle interrupts
-    fuse_req_interrupt_func(req, dbfs_interrupt_query, query);
-    
-    // wait
-    return;
-
-error:
-    if ((err = fuse_reply_err(req, err)))
-        EWARNING(err, "fuse_reply_err");
-}
-
-
-void dbfs_setattr (struct fuse_req *req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi) {
-    struct dbfs *ctx = fuse_req_userdata(req);
-    struct evsql_query *query;
-    int err;
-    int ret;
-    
-    char sql_buf[DBFS_SETATTR_SQL_MAX];
-    
-    static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) {
-        EVSQL_PARAM ( UINT16 ), // inodes.mode
-        EVSQL_PARAM ( UINT32 ), // inodes.uid
-        EVSQL_PARAM ( UINT32 ), // inodes.gid
-        EVSQL_PARAM ( UINT32 ), // data size
-        EVSQL_PARAM ( UINT32 ), // ino
-
-        EVSQL_PARAMS_END
-    };
-
-    // log
-    INFO("[dbfs.setattr %p] ino=%lu, fileop=%p: ", req, ino, fi && fi->fh ? (void*) fi->fh : NULL);
-    
-    if (to_set & FUSE_SET_ATTR_MODE) {
-        // ignore the S_IFMT
-        attr->st_mode &= 07777;
-
-        INFO("\tmode    = %08o", attr->st_mode);
-    }
-
-    if (to_set & FUSE_SET_ATTR_UID)
-        INFO("\tuid     = %u", attr->st_uid);
-
-    if (to_set & FUSE_SET_ATTR_GID)
-        INFO("\tgid     = %u", attr->st_gid);
-
-    if (to_set & FUSE_SET_ATTR_SIZE)
-        INFO("\tsize    = %lu", attr->st_size);
-
-    if (to_set & FUSE_SET_ATTR_ATIME)
-        INFO("\tatime   = %lu", attr->st_atime);
-
-    if (to_set & FUSE_SET_ATTR_MTIME)
-        INFO("\tmtime   = %lu", attr->st_mtime);
-
-    // the SQL
-    if ((ret = snprintf(sql_buf, DBFS_SETATTR_SQL_MAX,
-        "UPDATE inodes SET"
-        " %s%s%s%s ino = ino"
-        " WHERE inodes.ino = $5::int4"
-        " RETURNING inodes.ino, " DBFS_STAT_COLS,
-        
-        FIELD(to_set, FUSE_SET_ATTR_MODE,   "mode", "$1::int2"),
-        FIELD(to_set, FUSE_SET_ATTR_UID,    "uid",  "$2::int4"),
-        FIELD(to_set, FUSE_SET_ATTR_GID,    "gid",  "$3::int4"),
-        FIELD(to_set, FUSE_SET_ATTR_SIZE,   "data", "lo_otruncate(data, $4::int4)")
-    )) >= DBFS_SETATTR_SQL_MAX && (err = EIO))
-        ERROR("sql_buf is too small: %i", ret);
-    
-    // the params...
-    if (0
-        || (                                  evsql_params_clear(&params)                    )
-        || ((to_set & FUSE_SET_ATTR_MODE ) && evsql_param_uint16(&params, 0, attr->st_mode)  )
-        || ((to_set & FUSE_SET_ATTR_UID  ) && evsql_param_uint32(&params, 1, attr->st_uid)   )
-        || ((to_set & FUSE_SET_ATTR_GID  ) && evsql_param_uint32(&params, 2, attr->st_gid)   )
-        || ((to_set & FUSE_SET_ATTR_SIZE ) && evsql_param_uint32(&params, 3, attr->st_size)  )
-        || (                                  evsql_param_uint32(&params, 4, ino)            )
-    )
-        SERROR(err = EIO);
-
-    // trace the query
-    evsql_query_debug(sql_buf, &params);
-    
-    // query... we can pretend it's a getattr :)
-    if ((query = evsql_query_params(ctx->db, NULL, sql_buf, &params, _dbfs_attr_res, req)) == NULL)
-        SERROR(err = EIO);
-
-    // handle interrupts
-    fuse_req_interrupt_func(req, dbfs_interrupt_query, query);
-    
-    // wait
-    return;
-
-error:
-    if ((err = fuse_reply_err(req, err)))
-        EWARNING(err, "fuse_reply_err");
-}
--- a/src/dbfs/common.c	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,95 +0,0 @@
-
-#include <string.h>
-
-#include "dbfs.h"
-#include "../lib/log.h"
-
-mode_t _dbfs_mode (const char *type) {
-    if (!strcmp(type, "DIR"))
-        return S_IFDIR;
-
-    if (!strcmp(type, "REG"))
-        return S_IFREG;
-    
-    if (!strcmp(type, "LNK"))
-        return S_IFLNK;
-
-    else {
-        WARNING("[dbfs] weird mode-type: %s", type);
-        return 0;
-    }
-}
-
-int _dbfs_check_res (struct evsql_result *res, size_t rows, size_t cols) {
-    int err = 0;
-
-    // check if it failed
-    if (evsql_result_check(res))
-        NERROR(evsql_result_error(res));
-        
-    // not found?
-    if (evsql_result_rows(res) == 0 && evsql_result_affected(res) == 0)
-        SERROR(err = 1);
-
-    // duplicate rows?
-    if (rows && evsql_result_rows(res) != rows)
-        ERROR("wrong number of rows returned");
-    
-    // correct number of columns
-    if (evsql_result_cols(res) != cols)
-        ERROR("wrong number of columns: %zu", evsql_result_cols(res));
-
-    // good
-    return 0;
-
-error:
-    if (!err)
-        err = -1;
-
-    return err;
-}
-
-err_t dbfs_check_result (struct evsql_result *res, size_t rows, size_t cols) {
-    err_t err;
-
-    // number of rows returned/affected
-    size_t nrows = evsql_result_rows(res) ? : evsql_result_affected(res);
-
-    // did the query fail outright?
-    if (evsql_result_check(res))
-        // dump error message
-        NXERROR(err = EIO, evsql_result_error(res));
-    
-    // SELECT/DELETE/UPDATE WHERE didn't match any rows -> ENOENT
-    if (nrows == 0)
-        XERROR(err = ENOENT, "no rows returned/affected");
-    
-    // duplicate rows where one expected?
-    if (rows && nrows != rows)
-        XERROR(err = EIO, "wrong number of rows: %zu -> %zu", rows, nrows);
-    
-    // correct number of columns
-    if (evsql_result_cols(res) != cols)
-        XERROR(err = EIO, "wrong number of columns: %zu -> %zu", cols, evsql_result_cols(res));
-
-    // good
-    return 0;
-
-error:
-    return err;
-}
-
-int _dbfs_stat_info (struct stat *st, struct dbfs_stat_values *values) {
-    INFO("\tst_mode=S_IF%s | %ho, st_nlink=%llu, st_size=%llu", values->type, values->mode, (long long unsigned int) values->nlink, (long long unsigned int) values->size);
-
-    // convert and store
-    st->st_mode = _dbfs_mode(values->type) | values->mode;
-    st->st_nlink = values->nlink;
-    st->st_size = values->size;
-    
-    // good
-    return 0;
-}
-
-
-
--- a/src/dbfs/dbfs.c	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,100 +0,0 @@
-
-#include <stdlib.h>
-
-#include "dbfs.h"
-#include "../dbfs.h"
-#include "../lib/log.h"
-
-static struct fuse_lowlevel_ops dbfs_llops = {
-    .init           = dbfs_init,
-    .destroy        = dbfs_destroy,
-    .lookup         = dbfs_lookup,
-    // .forget                      // not needed
-    .getattr        = dbfs_getattr,
-    .setattr        = dbfs_setattr,
-    .readlink       = dbfs_readlink,
-    .mknod          = dbfs_mknod,
-    .mkdir          = dbfs_mkdir,
-    .unlink         = dbfs_unlink,
-    .rmdir          = dbfs_unlink,  // this behaves just the same
-    .symlink        = dbfs_symlink,
-    .rename         = dbfs_rename,
-    .link           = dbfs_link,
-    .open           = dbfs_open,
-    .read           = dbfs_read,
-    .write          = dbfs_write,
-    .flush          = dbfs_flush,
-    .release        = dbfs_release,
-    // .fsync                       // not needed
-    .opendir        = dbfs_opendir,
-    .readdir        = dbfs_readdir,
-    .releasedir     = dbfs_releasedir,
-};
-
-void dbfs_init (void *userdata, struct fuse_conn_info *conn) {
-    INFO("[dbfs.init] userdata=%p, conn=%p", userdata, conn);
-
-}
-
-void dbfs_destroy (void *arg) {
-    struct dbfs *ctx = arg;
-    INFO("[dbfs.destroy %p]", ctx);
-
-    // exit libevent
-    event_base_loopexit(ctx->ev_base, NULL);
-}
-
-
-void dbfs_sql_error (struct evsql *evsql, void *arg) {
-    struct dbfs *ctx = arg;
-
-    // AAAAAAAAAA.... panic
-    WARNING("[dbfs] SQL error: BREAKING MAIN LOOP LIEK NAO");
-
-    event_base_loopbreak(ctx->ev_base);
-}
-
-struct dbfs *dbfs_new (struct event_base *ev_base, struct fuse_args *args, const char *db_conninfo) {
-    struct dbfs *ctx = NULL;
-
-    // alloc ctx
-    if ((ctx = calloc(1, sizeof(*ctx))) == NULL)
-        ERROR("calloc");
-    
-    ctx->ev_base = ev_base;
-    ctx->db_conninfo = db_conninfo;
-
-    // open sql
-    if ((ctx->db = evsql_new_pq(ctx->ev_base, ctx->db_conninfo, dbfs_sql_error, ctx)) == NULL)
-        ERROR("evsql_new_pq");
-
-    // open fuse
-    if ((ctx->ev_fuse = evfuse_new(ctx->ev_base, args, &dbfs_llops, ctx)) == NULL)
-        ERROR("evfuse_new");
-
-    // success
-    return ctx;
-
-error:
-    if (ctx)
-        dbfs_free(ctx);
-
-    return NULL;
-}    
-
-void dbfs_free (struct dbfs *ctx) {
-    // cleanup
-    if (ctx->ev_fuse) {
-        evfuse_free(ctx->ev_fuse);
-    
-        ctx->ev_fuse = NULL;
-    }
-
-    if (ctx->db) {
-        // XXX: not yet implemented 
-        // evsql_close(ctx->db);
-        // ctx->db = NULL;
-    }
-    
-    free(ctx);
-}
--- a/src/dbfs/dbfs.h	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,130 +0,0 @@
-#ifndef DBFS_DBFS_H
-#define DBFS_DBFS_H
-
-#include <sys/stat.h>
-#include <errno.h>
-
-#include <event2/event.h>
-
-#include "ops.h"
-#include "../evfuse.h"
-#include "../evsql.h"
-#include "../lib/error.h"
-
-/*
- * Structs and functions shared between all dbfs components
- */
-
-struct dbfs {
-    struct event_base *ev_base;
-    
-    const char *db_conninfo;
-    struct evsql *db;
-
-    struct evfuse *ev_fuse;
-};
-
-// XXX: not sure how this should work
-#define CACHE_TIMEOUT 1.0
-
-/*
- * Convert the CHAR(4) inodes.type from SQL into a mode_t.
- *
- * Returns zero for unknown types.
- */
-mode_t _dbfs_mode (const char *type);
-
-/*
- * Check that the number of rows and columns in the result set matches what we expect.
- *
- * If rows is nonzero, there must be exactly that many rows (mostly useful for rows=1).
- * The number of columns must always be given, and match.
- *
- * Returns;
- *  -1  if the query failed, the columns/rows do not match
- *  0   the results match
- *  1   there were no results (zero rows)
- */
-int _dbfs_check_res (struct evsql_result *res, size_t rows, size_t cols);
-
-/*
- * Same as _dbfs_check_res, but returns ENOENT/EIO directly
- */
-err_t dbfs_check_result (struct evsql_result *res, size_t rows, size_t cols);
-
-/*
- * Stat fields
- */
-
-// columns used for stat_info
-#define DBFS_STAT_COLS " inodes.type, inodes.mode, dbfs_size(inodes.type, inodes.data, inodes.link_path), (SELECT COUNT(*) FROM inodes i LEFT JOIN file_tree ft ON (i.ino = ft.ino) WHERE i.ino = inodes.ino) AS nlink"
-
-
-#define DBFS_STAT_RESULT_INFO \
-    {   EVSQL_FMT_BINARY,   EVSQL_TYPE_STRING   },  \
-    {   EVSQL_FMT_BINARY,   EVSQL_TYPE_UINT16   },  \
-    {   EVSQL_FMT_BINARY,   EVSQL_TYPE_UINT32   },  \
-    {   EVSQL_FMT_BINARY,   EVSQL_TYPE_UINT64   }
-
-struct dbfs_stat_values {
-    const char *type;
-    uint16_t mode;
-    uint32_t size;
-    uint64_t nlink;
-};
-
-#define DBFS_STAT_RESULT_VALUES(ptr) \
-    &(ptr)->type, &(ptr)->mode, &(ptr)->size, &(ptr)->nlink
-
-/*
- * Fill a `struct state` with info retrieved from a SQL query.
- *
- * The result must contain four columns, starting at the given offset:
- *  inodes.type, inodes.mode, inodes.size, count(*) AS nlink
- *
- * Note that this does not fill the st_ino field
- */
-int _dbfs_stat_info (struct stat *st, struct dbfs_stat_values *values);
-
-/** interrupt.c 
- *  
- * Fuse interrupts are handled using fuse_req_interrupt_func. Calling this registers a callback function with the req,
- * which may or may not be called either by fuse_req_interrupt_func, or later on via evfuse's event handler. It is
- * assumed that this will never be called after a call to fuse_reply_*.
- *
- * Hence, to handle an interrupt, we must first ensure that fuse_reply_* will not be called afterwards (it'll return
- * an error), and then we must call fuse_reply_err(req, EINTR).
- *
- * In the simplest case, we can simply submit a query, and then abort it once the req is interrupted (now or later).
- * In the more complicated case, we can check if the request was interrupted, if not, do the query and handle
- * interrupts.
- */
-
-/*
- * Useable as a callback to fuse_req_interrupt_func, will abort the given query and err the req.
- *
- * Due to a locking bug in libfuse 2.7.4, this will actually delay the fuse_req_err until the next event-loop iteration.
- */
-void dbfs_interrupt_query (struct fuse_req *req, void *query_ptr);
-
-/*
- * XXX: More complicated state, is this actually needed?
- */
-struct dbfs_interrupt_ctx {
-    struct fuse_req *req;
-    struct evsql_query *query;
-
-    int interrupted : 1;
-};
-
-/*
- * Register as a fuse interrupt function for simple requests that only run one query without allocating any resources.
- *
- * This will abort the query if the interrupt is run, causing it's callback to not be called.
- *
- * Returns nonzero if the request was already interrupted, zero otherwise. Be careful that the interrupt does not get
- * fired between you checking for it and setting query.
- */
-int dbfs_interrupt_register (struct fuse_req *req, struct dbfs_interrupt_ctx *ctx);
-
-#endif /* DBFS_DBFS_H */
--- a/src/dbfs/dirop.c	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,303 +0,0 @@
-
-#include <stdlib.h>
-#include <assert.h>
-
-#include "dbfs.h"
-#include "op_base.h"
-#include "../dirbuf.h"
-#include "../lib/log.h"
-
-/*
- * Directory related functionality like opendir, readdir, releasedir
- */
-struct dbfs_dirop {
-    struct dbfs_op base;
-
-    // parent dir inodes
-    uint32_t parent;
-    
-    // for readdir
-    struct dirbuf dirbuf;
-};
-
-/*
- * Release the dirbuf.
- */
-static void _dbfs_dirop_free (struct dbfs_op *op_base) {
-    struct dbfs_dirop *dirop = (struct dbfs_dirop *) op_base;
-
-    // just release the dirbuf
-    dirbuf_release(&dirop->dirbuf);
-}
-
-/*
- * Handle the results for the initial attribute lookup for the dir itself during opendir ops.
- */
-static void dbfs_opendir_res (struct evsql_result *res, void *arg) {
-    struct dbfs_dirop *dirop = arg;
-    int err;
-    
-    assert(dirop->base.req);
-    assert(dirop->base.trans); // query callbacks don't get called if the trans fails
-    assert(!dirop->base.open);
-   
-    // check the results
-    if ((err = _dbfs_check_res(res, 1, 2)))
-        SERROR(err = (err ==  1 ? ENOENT : EIO));
-
-    const char *type;
-
-    // extract the data
-    if (0
-        ||  evsql_result_uint32(res, 0, 0, &dirop->parent,  1 ) // file_tree.parent
-        ||  evsql_result_string(res, 0, 1, &type,           0 ) // inodes.type
-    )
-        SERROR(err = EIO);
-
-    // is it a dir?
-    if (_dbfs_mode(type) != S_IFDIR)
-        EERROR(err = ENOTDIR, "wrong type: %s", type);
-    
-    INFO("\t[dbfs.opendir %p:%p] -> ino=%lu, parent=%lu, type=%s", dirop, dirop->base.req, (unsigned long int) dirop->base.ino, (unsigned long int) dirop->parent, type);
-    
-    // open_fn done, do the open_reply
-    if ((err = dbfs_op_open_reply(&dirop->base)))
-        goto error;
-
-    // success, fallthrough for evsql_result_free
-    err = 0;
-
-error:
-    if (err)
-        // fail it
-        dbfs_op_fail(&dirop->base, err);
-    
-    // free
-    evsql_result_free(res);
-}
-
-/*
- * The opendir transaction is ready for use. Query for the given dir's info
- */
-static void dbfs_dirop_open (struct dbfs_op *op_base) {
-    struct dbfs_dirop *dirop = (struct dbfs_dirop *) op_base;
-    struct dbfs *ctx = fuse_req_userdata(dirop->base.req);
-    int err;
-    
-    assert(dirop->base.trans); 
-    assert(dirop->base.req);
-    assert(!dirop->base.open);
-
-    INFO("\t[dbfs.opendir %p:%p] -> trans=%p", dirop, dirop->base.req, dirop->base.trans);
-    
-    // first fetch info about the dir itself
-    const char *sql =
-        "SELECT"
-        " file_tree.parent, inodes.type"
-        " FROM file_tree LEFT OUTER JOIN inodes ON (file_tree.ino = inodes.ino)"
-        " WHERE file_tree.ino = $1::int4";
-
-    static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) {
-        EVSQL_PARAM ( UINT32 ),
-
-        EVSQL_PARAMS_END
-    };
-
-    // build params
-    if (0
-        ||  evsql_param_uint32(&params, 0, dirop->base.ino)
-    )
-        SERROR(err = EIO);
-        
-    // query
-    if (evsql_query_params(ctx->db, dirop->base.trans, sql, &params, dbfs_opendir_res, dirop) == NULL)
-        SERROR(err = EIO);
-
-    // ok, wait for the info results
-    return;
-
-error:
-    // fail it
-    dbfs_op_fail(&dirop->base, err);
-}
-
-/*
- * Handle opendir(), this means starting a new op.
- */
-void dbfs_opendir (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi) {
-    struct dbfs *ctx = fuse_req_userdata(req);
-    struct dbfs_dirop *dirop = NULL;
-    int err;
-    
-    // allocate it
-    if ((dirop = calloc(1, sizeof(*dirop))) == NULL && (err = EIO))
-        ERROR("calloc");
-
-    // do the op_open
-    if ((err = dbfs_op_open(ctx, &dirop->base, req, ino, fi, _dbfs_dirop_free, dbfs_dirop_open)))
-        ERROR("dbfs_op_open");
-
-    INFO("[dbfs.opendir %p:%p] ino=%lu, fi=%p", dirop, req, ino, fi);
-    
-    // wait
-    return;
-
-error:
-    if (dirop) {
-        // we can fail normally
-        dbfs_op_fail(&dirop->base, err);
-
-    } else {
-        // must error out manually as we couldn't alloc the context
-        if ((err = fuse_reply_err(req, err)))
-            EWARNING(err, "fuse_reply_err");
-    }
-}
-
-/*
- * Got the list of files for our readdir() request.
- *
- * Fill up the dirbuf, and then send the reply.
- *
- */
-static void dbfs_readdir_res (struct evsql_result *res, void *arg) {
-    struct dbfs_dirop *dirop = arg;
-    int err;
-    size_t row;
-    
-    assert(dirop->base.req);
-    assert(dirop->base.trans); // query callbacks don't get called if the trans fails
-    assert(dirop->base.open);
-    
-    // check the results
-    if ((err = _dbfs_check_res(res, 0, 4)) < 0)
-        SERROR(err = EIO);
-        
-    INFO("\t[dbfs.readdir %p:%p] -> files: res_rows=%zu", dirop, dirop->base.req, evsql_result_rows(res));
-        
-    // iterate over the rows
-    for (row = 0; row < evsql_result_rows(res); row++) {
-        uint32_t off, ino;
-        const char *name, *type;
-
-        // extract the data
-        if (0
-            ||  evsql_result_uint32(res, row, 0, &off,          0 ) // file_tree.offset
-            ||  evsql_result_string(res, row, 1, &name,         0 ) // file_tree.name
-            ||  evsql_result_uint32(res, row, 2, &ino,          0 ) // inodes.ino
-            ||  evsql_result_string(res, row, 3, &type,         0 ) // inodes.type
-        )
-            SERROR(err = EIO);
-        
-        INFO("\t%zu: off=%lu+2, name=%s, ino=%lu, type=%s", row, (long unsigned int) off, name, (long unsigned int) ino, type);
-
-        // add to the dirbuf
-        // offsets are just offset + 2
-        if ((err = dirbuf_add(dirop->base.req, &dirop->dirbuf, off + 2, off + 3, name, ino, _dbfs_mode(type))) < 0 && (err = EIO))
-            ERROR("failed to add dirent for inode=%lu", (long unsigned int) ino);
-        
-        // stop if it's full
-        if (err > 0)
-            break;
-    }
-
-    // send it
-    if ((err = dirbuf_done(dirop->base.req, &dirop->dirbuf)))
-        EERROR(err, "failed to send buf");
-    
-    // handled the req
-    if ((err = dbfs_op_req_done(&dirop->base)))
-        goto error;
-
-    // good, fallthrough
-    err = 0;
-
-error:
-    if (err)
-        dbfs_op_fail(&dirop->base, err);
-
-    // free
-    evsql_result_free(res);
-}
-
-/*
- * Handle a readdir request. This will execute a SQL query inside the transaction to get the files at the given offset,
- * and dbfs_readdir_res will handle the results.
- *
- * If trans failed earlier, detect that and return an error.
- */
-void dbfs_readdir (struct fuse_req *req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) {
-    struct dbfs *ctx = fuse_req_userdata(req);
-    struct dbfs_dirop *dirop;
-    int err;
-
-    // get the op
-    if ((dirop = (struct dbfs_dirop *) dbfs_op_req(req, ino, fi)) == NULL)
-        return;
-    
-    INFO("[dbfs.readdir %p:%p] ino=%lu, size=%zu, off=%zu, fi=%p : trans=%p", dirop, req, ino, size, off, fi, dirop->base.trans);
-
-    // create the dirbuf
-    if (dirbuf_init(&dirop->dirbuf, size, off))
-        SERROR(err = EIO);
-
-    // add . and ..
-    // we set the next offset to 2, because all dirent offsets will be larger than that
-    // assume that these two should *always* fit
-    if ((err = (0
-        ||  dirbuf_add(req, &dirop->dirbuf, 0, 1, ".",   dirop->base.ino,    S_IFDIR )
-        ||  dirbuf_add(req, &dirop->dirbuf, 1, 2, "..",  
-                        dirop->parent ? dirop->parent : dirop->base.ino,     S_IFDIR )
-    )) && (err = EIO))
-        ERROR("failed to add . and .. dirents");
-
-    // select all relevant file entries
-    const char *sql = 
-        "SELECT"
-        " file_tree.\"offset\", file_tree.name, inodes.ino, inodes.type"
-        " FROM file_tree LEFT OUTER JOIN inodes ON (file_tree.ino = inodes.ino)"
-        " WHERE file_tree.parent = $1::int4 AND file_tree.\"offset\" >= $2::int4"
-        " ORDER BY file_tree.\"offset\""
-        " LIMIT $3::int4";
-
-    static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) {
-        EVSQL_PARAM ( UINT32 ),
-        EVSQL_PARAM ( UINT32 ),
-        EVSQL_PARAM ( UINT32 ),
-
-        EVSQL_PARAMS_END
-    };
-
-    // adjust offset to take . and .. into account
-    if (off > 2)
-        off -= 2;
-    
-    // build params
-    if (0
-        ||  evsql_param_uint32(&params, 0, ino)
-        ||  evsql_param_uint32(&params, 1, off)
-        ||  evsql_param_uint32(&params, 2, dirbuf_estimate(&dirop->dirbuf, 0))
-    )
-        SERROR(err = EIO);
-
-    // query
-    if (evsql_query_params(ctx->db, dirop->base.trans, sql, &params, dbfs_readdir_res, dirop) == NULL)
-        SERROR(err = EIO);
-
-    // good, wait
-    return;
-
-error:
-    dbfs_op_fail(&dirop->base, err);
-}
-
-/*
- * "For every [succesfull] opendir call there will be exactly one releasedir call."
- *
- * The dirop may be in a failed state.
- */
-void dbfs_releasedir (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi) {
-    // just passthrough to dbfs_op
-    dbfs_op_release(req, ino, fi);
-}
-
--- a/src/dbfs/fileop.c	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,310 +0,0 @@
-#include <stdlib.h>
-#include <assert.h>
-
-#include <postgresql/libpq/libpq-fs.h>
-
-#include "dbfs.h"
-#include "op_base.h"
-#include "../lib/log.h"
-
-/*
- * A file-operation, i.e. a sequence consisting of an OPEN, a multitude of READ/WRITE, followed by zero or more FLUSHes, and finally a single RELEASE.
- *
- * For historical reasons this opens a transaction and keeps it between open/release, but reads/writes now use the oid directly and are transactionless.
- */
-
-struct dbfs_fileop {
-    struct dbfs_op base;
-    
-    uint32_t oid;
-//    uint32_t lo_fd;
-};
-
-static void _dbfs_fileop_free (struct dbfs_op *op_base) {
-    struct dbfs_fileop *fop = (struct dbfs_fileop *) op_base;
-    
-    /* no-op */
-    (void) fop;
-}
-
-static void dbfs_open_res (struct evsql_result *res, void *arg) {
-    struct dbfs_fileop *fop = arg;
-    int err;
-
-    // check the results
-    if ((err = _dbfs_check_res(res, 1, 2)))
-        SERROR(err = (err ==  1 ? ENOENT : EIO));
-
-    const char *type;
-
-    // extract the data
-    if (0
-        ||  evsql_result_string(res, 0, 0, &type,           0 ) // inodes.type
-        ||  evsql_result_uint32(res, 0, 1, &fop->oid,       0 ) // inodes.data
-    )
-        SERROR(err = EIO);
-
-    // is it a dir?
-    if (_dbfs_mode(type) != S_IFREG)
-        EERROR(err = EINVAL, "wrong type: %s", type);
-    
-    INFO("\t[dbfs.open %p:%p] -> ino=%lu, type=%s", fop, fop->base.req, (unsigned long int) fop->base.ino, type);
-    
-    // open_fn done, do the open_reply
-    if ((err = dbfs_op_open_reply(&fop->base)))
-        goto error;
-
-    // success, fallthrough for evsql_result_free
-    err = 0;
-
-error:
-    if (err)
-        // fail it
-        dbfs_op_fail(&fop->base, err);
-    
-    // free
-    evsql_result_free(res);
-}
-
-static void dbfs_fileop_open (struct dbfs_op *op_base) {
-    struct dbfs_fileop *fop = (struct dbfs_fileop *) op_base;
-    struct dbfs *ctx = fuse_req_userdata(fop->base.req);
-    int err;
-    
-    // make sure the file actually exists
-    const char *sql =
-        "SELECT"
-        " inodes.type, inodes.data"
-        " FROM inodes"
-        " WHERE inodes.ino = $1::int4";
-
-    static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) {
-        EVSQL_PARAM ( UINT32 ),
-
-        EVSQL_PARAMS_END
-    };
-
-    // build params
-    if (0
-        ||  evsql_param_uint32(&params, 0, fop->base.ino)
-    )
-        SERROR(err = EIO);
-        
-    // query
-    if (evsql_query_params(ctx->db, fop->base.trans, sql, &params, dbfs_open_res, fop) == NULL)
-        SERROR(err = EIO);
-
-    // ok, wait for the info results
-    return;
-
-error:
-    // fail it
-    dbfs_op_fail(&fop->base, err);
-}
-
-void dbfs_open (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi) {
-    struct dbfs *ctx = fuse_req_userdata(req);
-    struct dbfs_fileop *fop = NULL;
-    int err;
-    
-    // allocate it
-    if ((fop = calloc(1, sizeof(*fop))) == NULL && (err = EIO))
-        ERROR("calloc");
-
-    // do the op_open
-    if ((err = dbfs_op_open(ctx, &fop->base, req, ino, fi, _dbfs_fileop_free, dbfs_fileop_open)))
-        ERROR("dbfs_op_open");
-    
-    // log
-    INFO("[dbfs.open %p:%p] ino=%lu, fi->flags=%04X", fop, req, ino, fi->flags);
-    
-    // wait
-    return;
-
-error:
-    if (fop) {
-        // we can fail normally
-        dbfs_op_fail(&fop->base, err);
-
-    } else {
-        // must error out manually as we couldn't alloc the context
-        if ((err = -fuse_reply_err(req, err)))
-            EWARNING(err, "fuse_reply_err");
-    }
-}
-
-void dbfs_read_res (struct evsql_result *res, void *arg) {
-    struct fuse_req *req = arg;
-    int err;
-    const char *buf;
-    size_t size;
- 
-    // check the results
-    if ((err = _dbfs_check_res(res, 1, 1)) < 0)
-        SERROR(err = EIO);
-        
-    // get the data
-    if (evsql_result_binary(res, 0, 0, &buf, &size, 0))
-        SERROR(err = EIO);
-
-    INFO("\t[dbfs.read %p] -> size=%zu", req, size);
-        
-    // send it
-    if ((err = -fuse_reply_buf(req, buf, size)))
-        EERROR(err, "fuse_reply_buf");
-    
-    // good, fallthrough
-    err = 0;
-
-error:
-    if (err)
-        fuse_reply_err(req, err);
-
-
-    // free
-    evsql_result_free(res);
-}
-
-void dbfs_read (struct fuse_req *req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) {
-    struct dbfs *ctx = fuse_req_userdata(req);
-    int err;
-    
-    // log
-    INFO("[dbfs.read %p] ino=%lu, size=%zu, off=%lu, fi->flags=%04X", req, ino, size, off, fi->flags);
-
-    // query
-    const char *sql = 
-        "SELECT"
-        " lo_pread_oid(data, $1::int4, $2::int4)"
-        " FROM inodes"
-        " WHERE ino = $3::int4";
-    
-    static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) {
-        EVSQL_PARAM ( UINT32 ), // len
-        EVSQL_PARAM ( UINT32 ), // off
-        EVSQL_PARAM ( UINT32 ), // ino
-
-        EVSQL_PARAMS_END
-    };
-
-    // build params
-    if (0
-        ||  evsql_param_uint32(&params, 0, size)
-        ||  evsql_param_uint32(&params, 1, off)
-        ||  evsql_param_uint32(&params, 2, ino)
-    )
-        SERROR(err = EIO);
-        
-    // query, transactionless
-    if (evsql_query_params(ctx->db, NULL, sql, &params, dbfs_read_res, req) == NULL)
-        SERROR(err = EIO);
-
-    // ok, wait for the info results
-    return;
-
-error:
-    fuse_reply_err(req, err);
-}
-
-void dbfs_write_res (struct evsql_result *res, void *arg) {
-    struct fuse_req *req = arg;
-    int err;
-    uint32_t size;
- 
-    // check the results
-    if ((err = _dbfs_check_res(res, 1, 1)) < 0)
-        SERROR(err = EIO);
-        
-    // get the size
-    if (evsql_result_uint32(res, 0, 0, &size, 0))
-        SERROR(err = EIO);
-
-    INFO("\t[dbfs.write %p] -> size=%lu", req, (long unsigned int) size);
-        
-    // send it
-    if ((err = -fuse_reply_write(req, size)))
-        EERROR(err, "fuse_reply_write");
-
-    // good, fallthrough
-    err = 0;
-
-error:
-    if (err)
-        fuse_reply_err(req, err);
-
-    // free
-    evsql_result_free(res);
-}
-
-void dbfs_write (struct fuse_req *req, fuse_ino_t ino, const char *buf, size_t size, off_t off, struct fuse_file_info *fi) {
-    struct dbfs *ctx = fuse_req_userdata(req);
-    int err;
-    
-    // log
-    INFO("[dbfs.write %p] ino=%lu, size=%zu, off=%lu, fi->flags=%04X", req, ino, size, off, fi->flags);
-
-    // query
-    const char *sql = 
-        "SELECT"
-        " lo_pwrite_oid(data, $1::bytea, $2::int4)"
-        " FROM inodes"
-        " WHERE ino = $3::int4";
-    
-    static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) {
-        EVSQL_PARAM ( BINARY ), // buf
-        EVSQL_PARAM ( UINT32 ), // off
-        EVSQL_PARAM ( UINT32 ), // oid
-
-        EVSQL_PARAMS_END
-    };
-
-    // build params
-    if (0
-        ||  evsql_param_binary(&params, 0, buf, size)
-        ||  evsql_param_uint32(&params, 1, off)
-        ||  evsql_param_uint32(&params, 2, ino)
-    )
-        SERROR(err = EIO);
-        
-    // query
-    if (evsql_query_params(ctx->db, NULL, sql, &params, dbfs_write_res, req) == NULL)
-        SERROR(err = EIO);
-
-    // ok, wait for the info results
-    return;
-
-error:
-    fuse_reply_err(req, err);
-}
-
-void dbfs_flush (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi) {
-    struct dbfs_fileop *fop;
-    int err;
-
-    // get the fop
-    if ((fop = (struct dbfs_fileop *) dbfs_op_req(req, ino, fi)) == NULL)
-        return;
-    
-    // log
-    INFO("[dbfs.flush %p:%p] ino=%lu", fop, req, ino);
-    
-    // and reply...
-    if ((err = -fuse_reply_err(req, 0)))
-        EWARNING(err, "fuse_reply_err");
-
-    // done
-    if ((err = dbfs_op_req_done(&fop->base)))
-        goto error;
-
-    // good
-    return;
-
-error:
-    dbfs_op_fail(&fop->base, err);
-}
-
-void dbfs_release (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi) {
-    // just passthrough to dbfs_op
-    // the lo_fd will be closed automatically
-    dbfs_op_release(req, ino, fi);
-}
--- a/src/dbfs/interrupt.c	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-
-#include "dbfs.h"
-
-void _dbfs_interrupt_reply (evutil_socket_t _unused1, short _unused2, void *req_ptr) {
-    struct fuse_req *req = req_ptr;
-    err_t err;
-    
-    // error the req
-    if ((err = -fuse_reply_err(req, EINTR)))
-        EWARNING(err, "fuse_reply_err");
-}
-
-void dbfs_interrupt_query (struct fuse_req *req, void *query_ptr) {
-    struct dbfs *ctx = fuse_req_userdata(req);
-    struct evsql_query *query = query_ptr;
-    struct timeval tv;
-    err_t err;
-
-    // abort query
-    evsql_query_abort(NULL, query);
-
-    // error the req
-    if ((err = -fuse_reply_err(req, EINTR)))
-        EWARNING(err, "fuse_reply_err");
-    
-    /*
-     * Due to a locking bug in libfuse (at least 2.7.4), we can't call fuse_reply_err from the interrupt function, so we must
-     * schedule after this function returns.
-     * /
-    tv.tv_sec = 0;
-    tv.tv_usec = 0;
-    if (event_base_once(ctx->ev_base, -1, EV_TIMEOUT, _dbfs_interrupt_reply, req, &tv))
-        PWARNING("event_base_once failed, dropping req reply: %p", req);
-        */
-}
-
-void _dbfs_interrupt_ctx (struct fuse_req *req, void *ctx_ptr) {
-    // dereference ctx
-    struct dbfs_interrupt_ctx *ctx = ctx_ptr;
-    
-    // just cancel query if pending
-    if (ctx->query) {
-        evsql_query_abort(NULL, ctx->query);
-        ctx->query = NULL;
-    }
-
-    // mark as interrupted
-    ctx->interrupted = 1;
-}
-
-int dbfs_interrupt_register (struct fuse_req *req, struct dbfs_interrupt_ctx *ctx) {
-    // initialize
-    ctx->query = NULL;
-    ctx->interrupted = 0;
-
-    // just pass over to fuse_req_interrupt_func
-    fuse_req_interrupt_func(req, _dbfs_interrupt_ctx, ctx);
-    
-    // XXX: not implemented fully
-    return -1;
-}
--- a/src/dbfs/link.c	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,285 +0,0 @@
-#include "dbfs.h"
-
-/*
- * Handling simple ino-related ops, like lookup, readlink, unlink and link
- */
-
-#include "../lib/log.h"
-#include "../lib/misc.h"
-
-/*
- * Used for lookup and link
- */
-void dbfs_entry_res (struct evsql_result *res, void *arg) {
-    struct fuse_req *req = arg;
-    struct fuse_entry_param e; ZINIT(e);
-    err_t err = 0;
-    
-    uint32_t ino;
-    struct dbfs_stat_values stat_values;
-
-    // result info
-    static struct evsql_result_info result_info = {
-        0, {
-            {   EVSQL_FMT_BINARY,   EVSQL_TYPE_UINT32   },  // inodes.ino
-            DBFS_STAT_RESULT_INFO,
-            {   0,                  0                   }
-        }
-    };
-
-    // begin
-    if ((err = evsql_result_begin(&result_info, res)))
-        EERROR(err, "query failed");
-
-    // get the one row of data
-    if ((err = evsql_result_next(res, &ino, DBFS_STAT_RESULT_VALUES(&stat_values))) <= 0)
-        EERROR(err = (err ? err : ENOENT), "evsql_result_next");
-   
-    INFO("\t[dbfs.lookup] -> ino=%u", ino);
-    
-    // stat attrs
-    if ((err = _dbfs_stat_info(&e.attr, &stat_values)))
-        goto error;
-    
-    // other attrs
-    e.ino = e.attr.st_ino = ino;
-    e.attr_timeout = CACHE_TIMEOUT;
-    e.entry_timeout = CACHE_TIMEOUT;
-        
-    // reply
-    if ((err = -fuse_reply_entry(req, &e)))
-        EERROR(err, "fuse_reply_entry");
-
-error:
-    if (err && (err = -fuse_reply_err(req, err)))
-        EWARNING(err, "fuse_reply_err");
-
-    // done
-    evsql_result_end(res);
-}
-
-void dbfs_lookup (struct fuse_req *req, fuse_ino_t parent, const char *name) {
-    struct dbfs *ctx = fuse_req_userdata(req);
-    struct evsql_query *query;
-    int err;
-
-    INFO("[dbfs.lookup] parent=%lu name=%s", parent, name);
-
-    // query info
-    static struct evsql_query_info query_info = {
-        .sql    =   "SELECT"
-                    " inodes.ino, " DBFS_STAT_COLS
-                    " FROM file_tree INNER JOIN inodes ON (file_tree.ino = inodes.ino)"
-                    " WHERE file_tree.parent = $1::int4 AND file_tree.name = $2::varchar",
-        
-        .params =   {
-            EVSQL_TYPE ( UINT32 ),
-            EVSQL_TYPE ( STRING ),
-
-            EVSQL_TYPE_END
-        }
-    };
-
-    // query
-    if ((query = evsql_query_exec(ctx->db, NULL, &query_info, dbfs_entry_res, req,
-        (uint32_t) parent,
-        (const char *) name
-    )) == NULL)
-        EERROR(err = EIO, "evsql_query_params");
-
-    // handle interrupts
-    fuse_req_interrupt_func(req, dbfs_interrupt_query, query);
-    
-    // wait
-    return;
-
-error:
-    if ((err = -fuse_reply_err(req, err)))
-        EWARNING(err, "fuse_reply_err");
-}
-
-void dbfs_readlink_res (struct evsql_result *res, void *arg) {
-    struct fuse_req *req = arg;
-    int err = 0;
-    
-    uint32_t ino;
-    const char *type, *link;
-
-    // result info
-    static struct evsql_result_info result_info = {
-        0, {
-            EVSQL_TYPE ( UINT32 ),
-            EVSQL_TYPE ( STRING ),
-            EVSQL_TYPE ( STRING ),
-
-            EVSQL_TYPE_END
-        }
-    };
-
-    // begin
-    if ((err = evsql_result_begin(&result_info, res)))
-        EERROR(err, "evsql_result_begin");
-
-    // get the row of data
-    if ((err = evsql_result_next(res,
-        &ino, &type, &link
-    )) <= 0)
-        SERROR(err = err || ENOENT);
-    
-    // is it a symlink?
-    if (_dbfs_mode(type) != S_IFLNK)
-        EERROR(err = EINVAL, "wrong type: %s", type);
-    
-    INFO("\t[dbfs.readlink %p] -> ino=%lu, type=%s, link=%s", req, (unsigned long int) ino, type, link);
-    
-    // reply
-    if ((err = -fuse_reply_readlink(req, link)))
-        EERROR(err, "fuse_reply_readlink");
-
-error:
-    if (err && (err = -fuse_reply_err(req, err)))
-        EWARNING(err, "fuse_reply_err");
-
-    // free
-    evsql_result_free(res);
-}
-
-void dbfs_readlink (struct fuse_req *req, fuse_ino_t ino) {
-    struct dbfs *ctx = fuse_req_userdata(req);
-    struct evsql_query *query;
-    int err;
-    
-    INFO("[dbfs.readlink %p] ino=%lu", req, ino);
-    
-    // query info
-    static struct evsql_query_info query_info = {
-        .sql    =   "SELECT"
-                    " inodes.ino, inodes.type, inodes.link_path"
-                    " FROM inodes"
-                    " WHERE inodes.ino = $1::int4",
-
-        .params =   {
-            EVSQL_TYPE ( UINT32 ),
-
-            EVSQL_TYPE_END
-        }
-    };
-
-    // query
-    if ((query = evsql_query_exec(ctx->db, NULL, &query_info, dbfs_readlink_res, req,
-        (uint32_t) ino
-    )) == NULL)
-        SERROR(err = EIO);
-
-    // handle interrupts
-    fuse_req_interrupt_func(req, dbfs_interrupt_query, query);
-    
-    // wait
-    return;
-
-error:
-    if ((err = -fuse_reply_err(req, err)))
-        EWARNING(err, "fuse_reply_err");
-
-}
-
-void dbfs_unlink_res (struct evsql_result *res, void *arg) {
-    struct fuse_req *req = arg;
-    int err = 0;
-
-    // check
-    if ((err = evsql_result_check(res)))
-        ERROR("evsql_result_check: %s", evsql_result_error(res));
-    
-    INFO("\t[dbfs.unlink %p] -> OK", req);
-    
-    // reply
-    if ((err = -fuse_reply_err(req, 0)))
-        EERROR(err, "fuse_reply_err");
-
-error:
-    if (err && (err = -fuse_reply_err(req, err)))
-        EWARNING(err, "fuse_reply_err");
-
-    // free
-    evsql_result_free(res);
-}
-
-void dbfs_unlink (struct fuse_req *req, fuse_ino_t parent, const char *name) {
-    struct dbfs *ctx = fuse_req_userdata(req);
-    struct evsql_query *query;
-    int err;
-    
-    INFO("[dbfs.unlink %p] parent=%lu, name=%s", req, parent, name);
-    
-    // query info
-    static struct evsql_query_info query_info = {
-        .sql    =   "DELETE"
-                    " FROM file_tree"
-                    " WHERE parent = $1::int4 AND name = $2::varchar",
-        
-        .params =   {
-            EVSQL_TYPE ( UINT32 ),
-            EVSQL_TYPE ( STRING ),
-
-            EVSQL_TYPE_END
-        }
-    };
-
-    // query
-    if ((query = evsql_query_exec(ctx->db, NULL, &query_info, dbfs_unlink_res, req,
-        (uint32_t) parent,
-        (const char *) name
-    )) == NULL)
-        SERROR(err = EIO);
-
-    // handle interrupts
-    fuse_req_interrupt_func(req, dbfs_interrupt_query, query);
-    
-    // wait
-    return;
-
-error:
-    if ((err = -fuse_reply_err(req, err)))
-        EWARNING(err, "fuse_reply_err");
-}
-
-void dbfs_link (struct fuse_req *req, fuse_ino_t ino, fuse_ino_t newparent, const char *newname) {
-    struct dbfs *ctx = fuse_req_userdata(req);
-    struct evsql_query *query;
-    int err;
-    
-    INFO("[dbfs.link %p] ino=%lu, newparent=%lu, newname=%s", req, ino, newparent, newname);
-
-    // query info
-    static struct evsql_query_info query_info = {
-        .sql    =   "SELECT ino, type, mode, size, nlink FROM dbfs_link($1::int4, $2::int4, $3::varchar)",
-
-        .params =   {
-            EVSQL_TYPE ( UINT32 ),
-            EVSQL_TYPE ( UINT32 ),
-            EVSQL_TYPE ( STRING ),
-
-            EVSQL_TYPE_END
-        }
-    };
-
-    // query
-    if ((query = evsql_query_exec(ctx->db, NULL, &query_info, dbfs_entry_res, req,
-        (uint32_t) ino,
-        (uint32_t) newparent,
-        (const char *) newname
-    )) == NULL)
-        SERROR(err = EIO);
-
-    // handle interrupts
-    fuse_req_interrupt_func(req, dbfs_interrupt_query, query);
-    
-    // wait
-    return;
-
-error:
-    if ((err = -fuse_reply_err(req, err)))
-        EWARNING(err, "fuse_reply_err");   
-}
-
--- a/src/dbfs/mk.c	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,243 +0,0 @@
-#include <stdlib.h>
-#include <assert.h>
-#include <string.h>
-
-#include "trans.h"
-#include "../lib/log.h"
-
-struct dbfs_mk_ctx {
-    struct dbfs_trans base;
-
-    const char *type, *data_expr;
-    char *link, *name;
-    uint16_t mode;
-    uint32_t ino, parent;
-
-    unsigned char is_dir : 1;
-};
-
-// default mode for symlinks
-#define DBFS_SYMLINK_MODE 0777
-
-// max. size for an dbfs_mk INSERT query
-#define DBFS_MK_SQL_MAX 512
-
-void dbfs_mk_free (struct dbfs_trans *ctx_base) {
-    struct dbfs_mk_ctx *ctx = (struct dbfs_mk_ctx *) ctx_base;
-    
-    free(ctx->link);
-    free(ctx->name);
-}
-
-void dbfs_mk_commit (struct dbfs_trans *ctx_base) {
-    struct dbfs_mk_ctx *ctx = (struct dbfs_mk_ctx *) ctx_base;
-    struct fuse_entry_param e;
-    int err;
-    
-    // build entry
-    e.ino = e.attr.st_ino = ctx->ino;
-    e.attr.st_mode = _dbfs_mode(ctx->type) | ctx->mode;
-    e.attr.st_size = ctx->link ? strlen(ctx->link) : 0;
-    e.attr.st_nlink = 1;
-    e.attr_timeout = e.entry_timeout = CACHE_TIMEOUT;
-
-    // reply
-    if ((err = fuse_reply_entry(ctx_base->req, &e)))
-        goto error;
-
-    // req good
-    ctx_base->req = NULL;
-    
-    // free
-    dbfs_trans_free(ctx_base);
-
-    // return
-    return;
-
-error:
-    dbfs_trans_fail(ctx_base, err);
-}
-
-void dbfs_mk_filetree (struct evsql_result *res, void *arg) {
-    struct dbfs_mk_ctx *ctx = arg;
-    int err = EIO;
-    
-    // check results
-    if (_dbfs_check_res(res, 0, 0) < 0)
-        goto error;
-    
-    // commit
-    dbfs_trans_commit(&ctx->base);
-
-    // fallthrough for result_free
-    err = 0;
-
-error:
-    if (err)
-        dbfs_trans_fail(&ctx->base, err);
-
-    evsql_result_free(res);
-}
-
-void dbfs_mk_inode (struct evsql_result *res, void *arg) {
-    struct dbfs_mk_ctx *ctx = arg;
-    struct dbfs *dbfs_ctx = fuse_req_userdata(ctx->base.req);
-    int err = EIO;
-    
-    // check result
-    if ((err = _dbfs_check_res(res, 1, 1)))
-        SERROR(err = err > 0 ? ENOENT : EIO);
-    
-    // get ino
-    if (evsql_result_uint32(res, 0, 0, &ctx->ino, 0))
-        goto error;
-
-    // insert file_tree entry
-    const char *sql = 
-        "INSERT"
-        " INTO file_tree (name, parent, ino, ino_dir)"
-        " VALUES ($1::varchar, $2::int4, $3::int4, $4::int4)";
-    
-    static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) {
-        EVSQL_PARAM ( STRING ),
-        EVSQL_PARAM ( UINT32 ),
-        EVSQL_PARAM ( UINT32 ),
-        EVSQL_PARAM ( UINT32 ),
-
-        EVSQL_PARAMS_END
-    };
-
-    if (0
-        ||  evsql_param_string(&params, 0, ctx->name)
-        ||  evsql_param_uint32(&params, 1, ctx->parent)
-        ||  evsql_param_uint32(&params, 2, ctx->ino)
-        ||  ctx->is_dir ? evsql_param_uint32(&params, 3, ctx->ino) : evsql_param_null(&params, 3)
-    )
-        goto error;
-    
-    // query
-    if (evsql_query_params(dbfs_ctx->db, ctx->base.trans, sql, &params, dbfs_mk_filetree, ctx))
-        goto error;
-    
-    // fallthrough for result_free
-    err = 0;
-
-error:
-    if (err)
-        dbfs_trans_fail(&ctx->base, err);
-
-    evsql_result_free(res);
-}
-
-void dbfs_mk_begin (struct dbfs_trans *ctx_base) {
-    struct dbfs_mk_ctx *ctx = (struct dbfs_mk_ctx *) ctx_base;
-    struct dbfs *dbfs_ctx = fuse_req_userdata(ctx_base->req);
-    int ret;
-
-    // insert inode
-    char sql_buf[DBFS_MK_SQL_MAX];
-    
-    if ((ret = snprintf(sql_buf, DBFS_MK_SQL_MAX, 
-        "INSERT"
-        " INTO inodes (type, mode, data, link_path)"
-        " VALUES ($1::char(3), $2::int2, %s, $3::varchar)"
-        " RETURNING inodes.ino", ctx->data_expr ? ctx->data_expr : "NULL"
-    )) >= DBFS_MK_SQL_MAX)
-        ERROR("sql_buf is too small: %d", ret);
-
-    static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) {
-        EVSQL_PARAM ( STRING ),
-        EVSQL_PARAM ( UINT16 ),
-        EVSQL_PARAM ( STRING ),
-
-        EVSQL_PARAMS_END
-    };
-
-    if (0
-        || evsql_param_string(&params, 0, ctx->type)
-        || evsql_param_uint16(&params, 1, ctx->mode)
-        || evsql_param_string(&params, 2, ctx->link)
-    )
-        goto error;
-    
-    if (evsql_query_params(dbfs_ctx->db, ctx_base->trans, sql_buf, &params, dbfs_mk_inode, ctx) == NULL)
-        goto error;
-    
-    return;
-
-error:
-    dbfs_trans_fail(ctx_base, EIO);
-}
-
-/*
- * It is assumed that name and link_path must be copied, but type remains useable
- */ 
-void dbfs_mk (struct fuse_req *req, fuse_ino_t parent, const char *name, const char *type, uint16_t mode, const char *data_expr, const char *link, unsigned char is_dir) {
-    struct dbfs_mk_ctx *ctx = NULL;
-
-    // alloc
-    if ((ctx = calloc(1, sizeof(*ctx))) == NULL)
-        ERROR("calloc");
-
-    // start trans
-    if (dbfs_trans_init(&ctx->base, req))
-        goto error;
- 
-    // callbacks
-    ctx->base.free_fn = dbfs_mk_free;
-    ctx->base.begin_fn = dbfs_mk_begin;
-    ctx->base.commit_fn = dbfs_mk_commit;
-   
-    // state
-    ctx->ino = 0;
-    ctx->parent = parent;
-    ctx->type = type;
-    ctx->data_expr = data_expr;
-    ctx->mode = mode;
-    ctx->is_dir = is_dir;
-
-    // copy volatile strings
-    if (
-            (link && (ctx->link = strdup(link)) == NULL)
-        ||  (name && (ctx->name = strdup(name)) == NULL)
-    )
-        ERROR("strdup");
-    
-    // log
-    INFO("[dbfs.mk %p:%p] parent=%lu, name=%s, type=%s, mode=%#04o data_expr=%s link=%s is_dir=%hhd", ctx, req, parent, name, type, mode, data_expr, link, is_dir);
-
-    // wait
-    return;
-
-error:
-    if (ctx)
-        dbfs_trans_fail(&ctx->base, EIO);
-}
-
-/*
- * These are all just aliases to dbfs_mk
- */ 
-void dbfs_mknod (struct fuse_req *req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev) {
-    int err;
-
-    if ((mode & S_IFMT) != S_IFREG)
-        EERROR(err = EINVAL, "mode is not REG: %#08o", mode);
-
-    dbfs_mk(req, parent, name, "REG", mode & 07777, "lo_create(0)", NULL, 0);
-
-    return;
-
-error:
-    if ((err = fuse_reply_err(req, err)))
-        EWARNING(err, "fuse_reply_error");
-}
-
-void dbfs_mkdir (struct fuse_req *req, fuse_ino_t parent, const char *name, mode_t mode) {
-    dbfs_mk(req, parent, name, "DIR", mode, NULL, NULL, 1);
-}
-
-
-void dbfs_symlink (struct fuse_req *req, const char *link, fuse_ino_t parent, const char *name) {
-    dbfs_mk(req, parent, name, "LNK", DBFS_SYMLINK_MODE, NULL, link, 0);
-}
-
--- a/src/dbfs/op_base.c	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,299 +0,0 @@
-#include <stdlib.h>
-#include <assert.h>
-
-#include "op_base.h"
-#include "../lib/log.h"
-
-/*
- * Free the op.
- *
- * The op must any oustanding request responded to first, must not be open, and must not have a transaction.
- *
- * The op will be free'd.
- */
-static void dbfs_op_free (struct dbfs_op *op) {
-    assert(op);
-    assert(!op->open);
-    assert(!op->req);
-    assert(!op->trans);
-
-    // free_fn
-    if (op->free_fn)
-        op->free_fn(op);
-    
-    // and then free the op
-    free(op);
-}
-
-void dbfs_op_fail (struct dbfs_op *op, int err) {
-    assert(op->req);
-    
-    if (op->trans) {
-        // abort the trans
-        evsql_trans_abort(op->trans);
-        
-        op->trans = NULL;
-    }
-
-    // send an error reply
-    if ((err = fuse_reply_err(op->req, err)))
-        // XXX: handle these failures /somehow/, or requests will hang and interrupts might handle invalid ops
-        EFATAL(err, "\tdbfs_op.fail %p:%p -> reply with fuse_reply_err", op, op->req);
-   
-    // drop the req
-    op->req = NULL;
-
-    // is it open?
-    if (!op->open) {
-        // no, we can free it now and then forget about the whole thing
-        dbfs_op_free(op);
-
-    } else {
-        // we need to wait for release
-
-    }
-}
-
-/*
- * The op_open transaction is ready for use.
- */
-static void dbfs_op_ready (struct evsql_trans *trans, void *arg) {
-    struct dbfs_op *op = arg;
-    
-    assert(trans == op->trans);
-    assert(op->req);
-    assert(!op->open);
-
-    INFO("\tdbfs_op.ready %p:%p -> trans=%p", op, op->req, trans);
-
-    // remember the transaction
-    op->trans = trans;
-
-    // ready
-    op->open_fn(op);
-    
-    // good
-    return;
-}
-
-/*
- * The op trans was committed, i.e. release has completed
- */
-static void dbfs_op_done (struct evsql_trans *trans, void *arg) {
-    struct dbfs_op *op = arg;
-    int err;
-    
-    assert(trans == op->trans);
-    assert(op->req);
-    assert(!op->open);   // should not be considered as open anymore at this point, as errors should release
-
-    INFO("\tdbfs_op.done %p:%p -> OK", op, op->req);
-
-    // forget trans
-    op->trans = NULL;
-    
-    // just reply
-    if ((err = fuse_reply_err(op->req, 0)))
-        // XXX: handle these failures /somehow/, or requests will hang and interrupts might handle invalid ops
-        EFATAL(err, "dbfs_op.done %p:%p -> reply with fuse_reply_err", op, op->req);
-    
-    // req is done
-    op->req = NULL;
-
-    // then we can just free op
-    dbfs_op_free(op);
-}
-
-/*
- * The op trans has failed, somehow, at some point, some where.
- *
- * This might happend during the open evsql_trans, during a read evsql_query, during the release
- * evsql_trans_commit, or at any point in between.
- *
- * 1) loose the transaction
- * 2) if op has a req, we handle failing it
- */
-static void dbfs_op_error (struct evsql_trans *trans, void *arg) {
-    struct dbfs_op *op = arg;
-    
-    // unless we fail 
-    assert(trans == op->trans);
-
-    INFO("\tdbfs_op.error %p:%p -> evsql transaction error: %s", op, op->req, evsql_trans_error(trans));
-    
-    // deassociate the trans
-    op->trans = NULL;
-    
-    // if we were answering a req, error it out, and if the op isn't open, free
-    // if we didn't have a req outstanding, the op must be open, so we wouldn't free it in any case, and must wait
-    // for the next read/release to detect this and return an error reply
-    if (op->req)
-        dbfs_op_fail(op, EIO);
-    else
-        assert(op->open);
-}
-
-int dbfs_op_open (struct dbfs *ctx, struct dbfs_op *op, struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi, dbfs_op_free_cb free_fn, dbfs_op_open_cb open_fn) {
-    int err;
-
-    assert(op && req && ino && fi);
-    assert(!(op->req || op->ino));
-
-    // initialize the op
-    op->req = req;
-    op->ino = ino;
-    // copy *fi since it's on the stack
-    op->fi = *fi;
-    op->fi.fh = (uint64_t) op;
-    op->free_fn = free_fn;
-    op->open_fn = open_fn;
-
-    // start a new transaction
-    if ((op->trans = evsql_trans(ctx->db, EVSQL_TRANS_SERIALIZABLE, dbfs_op_error, dbfs_op_ready, dbfs_op_done, op)) == NULL)
-        SERROR(err = EIO);
-    
-    // XXX: handle interrupts
-    
-    // good
-    return 0;
-
-error:
-    // nothing of ours to cleanup
-    return err;
-}
-
-int dbfs_op_open_reply (struct dbfs_op *op) {
-    int err;
-    
-    // detect earlier failures
-    if (!op->trans && (err = EIO))
-        ERROR("op trans has failed");
-
-    // send the openddir reply
-    if ((err = fuse_reply_open(op->req, &op->fi)))
-        EERROR(err, "fuse_reply_open");
-    
-    // req is done
-    op->req = NULL;
-
-    // op is now open
-    op->open = 1;
- 
-    // good
-    return 0;
-
-error:
-    return err;
-}
-
-struct dbfs_op *dbfs_op_req (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi) {
-    struct dbfs_op *op = (struct dbfs_op *) fi->fh;
-    int err;
-    
-    // validate
-    assert(op);
-    assert(op->open);
-    assert(op->ino == ino);
-    
-    // detect concurrent requests
-    if (op->req) {
-        // must handle req ourself, shouldn't fail the other req
-        WARNING("op.%p: concurrent req: %p -> %p", op, op->req, req);
-        
-        // XXX: ignore error errors...
-        fuse_reply_err(req, EBUSY);
-
-        return NULL;
-
-    } else
-        // store the new req
-        op->req = req;
-    
-    // inodes change?
-    if (op->ino != ino)
-        XERROR(err = EBADF, "op.%p: wrong ino: %u -> %lu", op, op->ino, ino);
-    
-    // detect earlier failures
-    if (!op->trans && (err = EIO))
-        ERROR("op trans has failed");
-
-    // good
-    return op;
-
-error:
-    dbfs_op_fail(op, err);
-    
-    return NULL;
-}
-
-int dbfs_op_req_done (struct dbfs_op *op) {
-    // validate
-    assert(op);
-    assert(op->req);
-    assert(op->open);
-
-    // unassign the req
-    op->req = NULL;
-
-    // k
-    return 0;
-}
-
-void dbfs_op_release (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi) {
-    struct dbfs_op *op = (struct dbfs_op *) fi->fh;
-    int err;
-    
-    assert(op);
-    assert(!op->req);
-    assert(op->ino == ino);
-    
-    // update to this req
-    op->req = req;
-
-    // fi is irrelevant, we don't touch the flags anyways
-    (void) fi;
-
-    // handle failed trans
-    if (!op->trans && (err = EIO))
-        ERROR("trans has failed");
-    
-    // log
-    INFO("\tdbfs_op.release %p:%p : ino=%lu, fi=%p : trans=%p", op, req, ino, fi, op->trans);
-    
-    // we must commit the transaction.
-    // Note that this might cause dbfs_op_error to be called, we can tell if that happaned by looking at op->req
-    // or op->trans - this means that we need to keep the op open when calling trans_commit, so that op_error
-    // doesn't free it out from underneath us.
-    if (evsql_trans_commit(op->trans))
-        SERROR(err = EIO);
-
-    // fall-through to cleanup
-    err = 0;
-
-error:
-    // the op is not open anymore and can be free'd next, because we either:
-    // a) already caught an error
-    // b) we get+send an error later on
-    // c) we get+send the done/no-error later on
-    op->open = 0;
-
-    // did the commit/pre-commit-checks fail?
-    if (err) {
-        // a) the trans failed earlier (read), so we have a req but no trans
-        // b) the trans commit failed, op_error got called -> no req and no trans
-        // c) the trans commit failed, op_error did not get called -> have req and trans
-        // we either have a req (may or may not have trans), or we don't have a trans either
-        // i.e. there is no situation where we don't have a req but do have a trans
-
-        if (op->req)
-            dbfs_op_fail(op, err);
-        else
-            assert(!op->trans);
-
-    } else {
-        // shouldn't slip by, op_done should not get called directly. Once it does, it will handle both.
-        assert(op->req);
-        assert(op->trans);
-    }
-}
-
--- a/src/dbfs/op_base.h	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,96 +0,0 @@
-#ifndef DBFS_OP_BASE_H
-#define DBFS_OP_BASE_H
-
-#include "dbfs.h"
-
-// forward-declaration for callbacks
-struct dbfs_op;
-
-/*
- * Called by dbfs_op_free to release any resources when the op is free'd (i.e. not open anymore).
- */
-typedef void (*dbfs_op_free_cb) (struct dbfs_op *op_base);
-
-/*
- * Called after the transaction has been opened, and before reply_open.
- *
- * You can do any at-open initialization here.
- */
-typedef void (*dbfs_op_open_cb) (struct dbfs_op *op_base);
-
-// the base op state
-struct dbfs_op {
-    struct fuse_file_info fi;
-    struct fuse_req *req;
-
-    struct evsql_trans *trans;
-    
-    // op target inode
-    uint32_t ino;
-    
-    // open has returned and release hasn't been called yet
-    int open;
-
-    // callbacks
-    dbfs_op_free_cb free_fn;
-    dbfs_op_open_cb open_fn;
-};
-
-/*
- * This will handle failures during requests.
- *
- * 1) if we have a trans, abort it
- * 2) fail the req (mandatory) with the given err
- *
- * If the op is open, then we don't release it, but if it's not open, then the op will be free'd completely.
- *
- */
-void dbfs_op_fail (struct dbfs_op *op, int err);
-
-/*
- * Open the op, that is, store all the initial state, and open a new transaction.
- *
- * The op must be pre-allocated and zero-initialized.
- *
- * This will always set op->req, so op is safe for dbfs_op_fail after this.
- *
- * This does not fail the dirop, handle error replies yourself.
- *
- * Returns zero on success, err on failure.
- */
-int dbfs_op_open (struct dbfs *ctx, struct dbfs_op *op, struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi, dbfs_op_free_cb free_fn, dbfs_op_open_cb ready_fn);
-
-/*
- * Should be called from open_fn to send the fuse_reply_open with fi and mark the op as open.
- *
- * If the op has failed earlier or fuse_reply_open fails, this will return nonzero. Fail the op yourself.
- */ 
-int dbfs_op_open_reply (struct dbfs_op *op);
-
-/*
- * Start handling a normal op requests.
- *
- * Lookup the op for the given fi, validate params, and assign the new req.
- *
- * In case the op failed previously, this will error the req and return NULL, indicating that the req has been handled.
- *
- * Repeat, if this returns NULL, consider req invalid.
- */
-struct dbfs_op *dbfs_op_req (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi);
-
-/*
- * Done handling a request, adjust state accordingly.
- *
- * req *must* have been replied to.
- */
-int dbfs_op_req_done (struct dbfs_op *op);
-
-/*
- * Handle the op release.
- *
- * This will take care of committing the transaction, sending any reply/error, closing the op and freeing it.
- */
-void dbfs_op_release (struct fuse_req *req, fuse_ino_t, struct fuse_file_info *fi);
-
-
-#endif /* DBFS_OP_BASE_H */
--- a/src/dbfs/ops.h	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,40 +0,0 @@
-#ifndef DBFS_OPS_H
-#define DBFS_OPS_H
-
-#include "../evfuse.h"
-
-/* dbfs.c */
-void dbfs_init (void *userdata, struct fuse_conn_info *conn);
-void dbfs_destroy (void *arg);
-
-/* attr.c */
-void dbfs_getattr (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi);
-void dbfs_setattr(struct fuse_req *req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi);
-
-/* link.c */
-void dbfs_lookup (struct fuse_req *req, fuse_ino_t parent, const char *name);
-void dbfs_readlink (struct fuse_req *req, fuse_ino_t ino);
-void dbfs_unlink (struct fuse_req *req, fuse_ino_t parent, const char *name);
-void dbfs_link (struct fuse_req *req, fuse_ino_t ino, fuse_ino_t newparent, const char *newname);
-
-/* dirop.c */
-void dbfs_opendir (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi);
-void dbfs_readdir (struct fuse_req *req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi);
-void dbfs_releasedir (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi);
-
-/* mk.c */
-void dbfs_mknod (struct fuse_req *req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev);
-void dbfs_mkdir (struct fuse_req *req, fuse_ino_t parent, const char *name, mode_t mode);
-void dbfs_symlink (struct fuse_req *req, const char *link, fuse_ino_t parent, const char *name);
-
-/* tree.c */
-void dbfs_rename (struct fuse_req *req, fuse_ino_t parent, const char *name, fuse_ino_t newparent, const char *newname);
-
-/* fileop.c */
-void dbfs_open (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi);
-void dbfs_read (struct fuse_req *req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi);
-void dbfs_write (struct fuse_req *req, fuse_ino_t ino, const char *buf, size_t size, off_t off, struct fuse_file_info *fi);
-void dbfs_flush (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi);
-void dbfs_release (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi);
-
-#endif /* DBFS_OPS_H */
--- a/src/dbfs/trans.c	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,138 +0,0 @@
-
-#include <stdlib.h>
-#include <assert.h>
-
-#include "trans.h"
-#include "../lib/log.h"
-
-void dbfs_trans_free (struct dbfs_trans *ctx) {
-    assert(!ctx->req);
-    assert(!ctx->trans);
-    
-    if (ctx->free_fn)
-        ctx->free_fn(ctx);
-
-    free(ctx);
-}
-
-void dbfs_trans_fail (struct dbfs_trans *ctx, int err) {
-    if (ctx->req) {
-        if ((err = fuse_reply_err(ctx->req, err)))
-            EWARNING(err, "fuse_reply_err: request hangs");
-
-        ctx->req = NULL;
-    }
-
-    if (ctx->trans) {
-        evsql_trans_abort(ctx->trans);
-
-        ctx->trans = NULL;
-    }
-
-    dbfs_trans_free(ctx);
-}
-
-static void dbfs_trans_error (struct evsql_trans *trans, void *arg) {
-    struct dbfs_trans *ctx = arg;
-
-    // deassociate trans
-    ctx->trans = NULL;
-
-    // log error
-    INFO("\t[dbfs_trans.err %p:%p] %s", ctx, ctx->req, evsql_trans_error(trans));
-
-    // mark
-    if (ctx->err_ptr)
-        *ctx->err_ptr = EIO;
-    
-    // fail
-    dbfs_trans_fail(ctx, EIO);
-}
-
-static void dbfs_trans_ready (struct evsql_trans *trans, void *arg) {
-    struct dbfs_trans *ctx = arg;
-
-    // associate trans
-    ctx->trans = trans;
-
-    // log
-    INFO("\t[dbfs_trans.ready %p:%p] -> trans=%p", ctx, ctx->req, trans);
-
-    // trigger the callback
-    ctx->begin_fn(ctx);
-}
-
-static void dbfs_trans_done (struct evsql_trans *trans, void *arg) {
-    struct dbfs_trans *ctx = arg;
-
-    // deassociate trans
-    ctx->trans = NULL;
-
-    // log
-    INFO("\t[dbfs_trans.done %p:%p]", ctx, ctx->req);
-
-    // trigger the callback
-    ctx->commit_fn(ctx);
-}
-
-int dbfs_trans_init (struct dbfs_trans *ctx, struct fuse_req *req) {
-    struct dbfs *dbfs_ctx = fuse_req_userdata(req);
-    int err;
-
-    // store
-    ctx->req = req;
-
-    // trans
-    if ((ctx->trans = evsql_trans(dbfs_ctx->db, EVSQL_TRANS_SERIALIZABLE, dbfs_trans_error, dbfs_trans_ready, dbfs_trans_done, ctx)) == NULL)
-        EERROR(err = EIO, "evsql_trans");
-
-    // good
-    return 0;
-
-error:
-    return -1;
-}
-
-struct dbfs_trans *dbfs_trans_new (struct fuse_req *req) {
-    struct dbfs_trans *ctx = NULL;
-
-    // alloc
-    if ((ctx = calloc(1, sizeof(*ctx))) == NULL)
-        ERROR("calloc");
-
-    // init
-    if (dbfs_trans_init(ctx, req))
-        goto error;
-
-    // good
-    return ctx;
-    
-error:
-    free(ctx);
-
-    return NULL;
-}
-
-void dbfs_trans_commit (struct dbfs_trans *ctx) {
-    int err, trans_err = 0;
-
-    // detect errors
-    ctx->err_ptr = &trans_err;
-    
-    // attempt commit
-    if (evsql_trans_commit(ctx->trans))
-        SERROR(err = EIO);
-    
-    // drop err_ptr
-    ctx->err_ptr = NULL;
-
-    // ok, wait for done or error
-    return;
-
-error:
-    // fail if not already failed
-    if (!trans_err)
-        dbfs_trans_fail(ctx, err);
-}
-
-
--- a/src/dbfs/trans.h	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +0,0 @@
-#ifndef DBFS_TRANS_H
-#define DBFS_TRANS_H
-
-/*
- * Support for single-fuse_req transactions.
- */
-
-#include "dbfs.h"
-
-// forward-declaration
-struct dbfs_trans;
-
-// generic callback
-typedef void (*dbfs_trans_cb) (struct dbfs_trans *ctx);
-
-/*
- * Request/transaction state.
- */
-struct dbfs_trans {
-    struct fuse_req *req;
-    struct evsql_trans *trans;
-    
-    // called when the dbfs_trans is being free'd
-    dbfs_trans_cb free_fn;
-
-    // called once the transaction is ready
-    dbfs_trans_cb begin_fn;
-
-    // called once the transaction has been commited
-    dbfs_trans_cb commit_fn;
-
-    // set by trans_error to EIO if !NULL
-    int *err_ptr;
-};
-
-/*
- * Call from commit_fn once you've set req to NULL. Also called from trans_fail.
- *
- * Will call free_fn if present.
- */
-void dbfs_trans_free (struct dbfs_trans *ctx);
-
-/*
- * Fail the dbfs_trans, aborting any trans, erroring out any req and freeing the ctx.
- */
-void dbfs_trans_fail (struct dbfs_trans *ctx, int err);
-
-/*
- * Initialize the ctx with the given req (stays the same during the lifetime), and opens the transaction.
- *
- * You should set the callback functions after/before calling this.
- *
- * begin_fn will be called once the transaction is open, if that fails, the req will be errored for you.
- *
- * If opening the transaction fails, this will return nonzero and not touch req, otherwise zero.
- */
-int dbfs_trans_init (struct dbfs_trans *ctx, struct fuse_req *req);
-
-/*
- * Same as init, but allocates/frees-on-error the dbfs_trans for you.
- */
-struct dbfs_trans *dbfs_trans_new (struct fuse_req *req);
-
-/*
- * Commit the dbfs_trans. After calling this, the ctx may or may not be valid, and commit_fn may or may not be called.
- */
-void dbfs_trans_commit (struct dbfs_trans *ctx);
-
-#endif /* DBFS_TRANS_H */
--- a/src/dbfs/tree.c	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,71 +0,0 @@
-
-#include "../lib/log.h"
-
-#include "dbfs.h"
-
-void dbfs_rename_res (struct evsql_result *res, void *arg) {
-    struct fuse_req *req = arg;
-    int err;
-
-    // check the results
-    if ((err = _dbfs_check_res(res, 0, 0)))
-        SERROR(err = (err ==  1 ? ENOENT : EIO));
-    
-    // just reply
-    if ((err = -fuse_reply_err(req, 0)))
-        EERROR(err, "fuse_reply_err");
-    
-    // log
-    INFO("[dbfs.rename %p] -> OK", req);
-
-    // fallthrough for result_free
-    err = 0;
-
-error:
-    if (err && (err = -fuse_reply_err(req, err)))
-        EWARNING(err, "fuse_reply_err");
-    
-    evsql_result_free(res);
-}
-
-void dbfs_rename (struct fuse_req *req, fuse_ino_t parent, const char *name, fuse_ino_t newparent, const char *newname) {
-    struct dbfs *dbfs_ctx = fuse_req_userdata(req);
-    int err;
-    
-    INFO("[dbfs.rename %p] parent=%lu, name=%s, newparent=%lu, newname=%s", req, parent, name, newparent, newname);
-
-    // just one UPDATE
-    const char *sql = 
-        "UPDATE"
-        " file_tree"
-        " SET parent = $1::int4, name = $2::varchar"
-        " WHERE parent = $3::int4 AND name = $4::varchar";
-
-    static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) {
-        EVSQL_PARAM ( UINT32 ),
-        EVSQL_PARAM ( STRING ),
-        EVSQL_PARAM ( UINT32 ),
-        EVSQL_PARAM ( STRING ),
-
-        EVSQL_PARAMS_END
-    };
-
-    if (0
-        ||  evsql_param_uint32(&params, 0, newparent)
-        ||  evsql_param_string(&params, 1, newname)
-        ||  evsql_param_uint32(&params, 2, parent)
-        ||  evsql_param_string(&params, 3, name)
-    )
-        SERROR(err = EIO);
-
-    // query
-    if (evsql_query_params(dbfs_ctx->db, NULL, sql, &params, dbfs_rename_res, req) == NULL)
-        SERROR(err = EIO);
-    
-    // good, wait
-    return;
-
-error:
-    if ((err = fuse_reply_err(req, err)))
-        EWARNING(err, "fuse_reply_err");
-}
--- a/src/dirbuf.c	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,87 +0,0 @@
-
-#include <stdlib.h>
-
-#include "dirbuf.h"
-#include "lib/log.h"
-#include "lib/math.h"
-
-int dirbuf_init (struct dirbuf *buf, size_t req_size, off_t req_off) {
-    buf->buf = NULL;
-    buf->len = req_size;
-    buf->off = 0;
-    buf->req_off = req_off;
-    
-    DEBUG("\tdirbuf.init: req_size=%zu", req_size);
-
-    // allocate the mem
-    if ((buf->buf = malloc(buf->len)) == NULL)
-        ERROR("malloc");
-    
-    // ok
-    return 0;
-
-error:
-    return -1;
-}
-
-size_t dirbuf_estimate (struct dirbuf *buf, size_t min_namelen) {
-    char namebuf[DIRBUF_NAME_MAX];
-    int i;
-    
-    // build a dummy string of the right length
-    for (i = 0; i < min_namelen && i < DIRBUF_NAME_MAX - 1; i++)
-        namebuf[i] = 'x';
-
-    namebuf[i] = '\0';
-
-    return buf->len / (fuse_add_direntry(NULL, NULL, 0, namebuf, NULL, 0));
-}
-
-int dirbuf_add (fuse_req_t req, struct dirbuf *buf, off_t ent_off, off_t next_off, const char *ent_name, fuse_ino_t ent_ino, mode_t ent_mode) {
-    struct stat stbuf;
-    size_t ent_size;
-
-    DEBUG("\tdirbuf.add: req_off=%zu, buf->len=%zu, buf->off=%zu, ent_off=%zu, next_off=%zu, ent_name=`%s`, ent_ino=%lu, ent_mode=%07o",
-        buf->req_off, buf->len, buf->off, ent_off, next_off, ent_name, ent_ino, ent_mode);
-    
-    // skip entries as needed
-    if (ent_off < buf->req_off) 
-        return 0;
-
-    // set ino
-    stbuf.st_ino = ent_ino;
-    stbuf.st_mode = ent_mode;
-    
-    // try and add the dirent, and see if it fits
-    if ((ent_size = fuse_add_direntry(req, buf->buf + buf->off, buf->len - buf->off, ent_name, &stbuf, next_off)) > (buf->len - buf->off)) {
-        // 'tis full
-        return 1;
-
-    } else {
-        // it fit
-        buf->off += ent_size;
-    }
-
-    // success
-    return 0;
-}
-
-int dirbuf_done (fuse_req_t req, struct dirbuf *buf) {
-    int err;
-    
-    // send the reply, return the error later
-    err = -fuse_reply_buf(req, buf->buf, buf->off);
-
-    DEBUG("\tdirbuf.done: size=%zu/%zu, err=%d", buf->off, buf->len, err);
-
-    // free the dirbuf
-    dirbuf_release(buf);
-
-    // return the error code
-    return err;
-}
-
-void dirbuf_release (struct dirbuf *buf) {
-    free(buf->buf); buf->buf = NULL;
-}
-
--- a/src/dirbuf.h	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +0,0 @@
-#ifndef DIRBUF_H
-#define DIRBUF_H
-
-/*
- * Simple dirent building
- */
-
-#include "evfuse.h"
-
-/*
- * Holds the dir entries
- */ 
-struct dirbuf {
-    char *buf;
-    size_t len;
-    off_t off, req_off;
-};
-
-// maximum length for a dirbuf name, including NUL byte
-#define DIRBUF_NAME_MAX 256
-
-/*
- * Initialize a dirbuf for a request. The dirbuf will be filled with at most req_size bytes of dir entries.
- *
- *  req_size    - how many bytes of dirbuf data we want, at most 
- *  req_off     - the offset of the first dirent to include
- */
-int dirbuf_init (struct dirbuf *buf, size_t req_size, off_t req_off);
-
-/*
- * Estimate how many dir entries will, at most, fit into a difbuf of the given size, based on a minimum filename size.
- */
-size_t dirbuf_estimate (struct dirbuf *buf, size_t min_namelen);
-
-/*
- * Add an dir entry to the dirbuf. The dirbuf should not be full.
- *
- * Offsets are followed:
- *  ent_off     - the offset of this dirent
- *  next_off    - the offset of the next dirent
- *
- * Only the S_IFMT bits of ent_mode are relevant.
- *
- * Returns 0 if the ent was added or skipped, -1 on error, 1 if the dirbuf is full (no more ents should be added).
- */
-int dirbuf_add (fuse_req_t req, struct dirbuf *buf, off_t ent_off, off_t next_off, const char *ent_name, fuse_ino_t ent_ino, mode_t ent_mode);
-
-/*
- * Attempt to send the readdir reply, free the buf, and return the error code from fuse_reply_buf
- */
-int dirbuf_done (fuse_req_t req, struct dirbuf *buf);
-
-/*
- * Release the dirop without sending any reply back.
- *
- * This is safe to be called multiple times.
- */
-void dirbuf_release (struct dirbuf *buf);
-
-#endif /* DIRBUF_H */
--- a/src/evfuse.c	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,153 +0,0 @@
-
-#include <errno.h>
-#include <string.h>
-#include <stdlib.h>
-
-#include "evfuse.h"
-#include "lib/log.h"
-
-struct evfuse {
-    // our mountpoint
-    char *mountpoint;
-
-    // the /dev/fuse fd/channel that we get from fuse_mount
-    struct fuse_chan *chan;
-
-    // the session that we use to process the fuse stuff
-    struct fuse_session *session;
-
-    // the event that we use to receive requests
-    struct event *ev;
-    
-    // what our receive-message length is
-    size_t recv_size;
-
-    // the buffer that we use to receive events
-    char *recv_buf;
-};
-
-// prototypes
-void evfuse_close (struct evfuse *ctx);
-
-static void _evfuse_ev_read (evutil_socket_t fd, short what, void *arg) {
-    struct evfuse *ctx = arg;
-    struct fuse_chan *ch = ctx->chan;
-    int res;
-    
-    // loop until we complete a recv
-    do {
-        // a new fuse_req is available
-        res = fuse_chan_recv(&ch, ctx->recv_buf, ctx->recv_size);
-    } while (res == -EINTR);
-
-    if (res == 0)
-        ERROR("fuse_chan_recv gave EOF");
-
-    if (res < 0 && res != -EAGAIN)
-        ERROR("fuse_chan_recv failed: %s", strerror(-res));
-    
-    if (res > 0) {
-        DEBUG("got %d bytes from /dev/fuse", res);
-
-        // received a fuse_req, so process it
-        fuse_session_process(ctx->session, ctx->recv_buf, res, ch);
-    }
-    
-    // reschedule
-    if (event_add(ctx->ev, NULL))
-        PERROR("event_add");
-
-    // ok, wait for the next event
-    return;
-
-error:
-    // close, but don't free
-    evfuse_close(ctx);
-}
-
-struct evfuse *evfuse_new (struct event_base *evbase, struct fuse_args *args, struct fuse_lowlevel_ops *llops, void *cb_data) {
-    struct evfuse *ctx = NULL;
-    int multithreaded, foreground;
-    
-    // allocate our context
-    if ((ctx = calloc(1, sizeof(*ctx))) == NULL)
-        ERROR("calloc");
-
-    // parse the commandline for the mountpoint
-    if (fuse_parse_cmdline(args, &ctx->mountpoint, &multithreaded, &foreground) == -1)
-        ERROR("fuse_parse_cmdline");
-
-    // mount it
-    if ((ctx->chan = fuse_mount(ctx->mountpoint, args)) == NULL)
-        PERROR("fuse_mount_common");
-
-    // the receive buffer stufff
-    ctx->recv_size = fuse_chan_bufsize(ctx->chan);
-
-    // allocate the recv buffer
-    if ((ctx->recv_buf = malloc(ctx->recv_size)) == NULL)
-        ERROR("malloc");
-    
-    // allocate a low-level session
-    if ((ctx->session = fuse_lowlevel_new(args, llops, sizeof(*llops), cb_data)) == NULL)
-        PERROR("fuse_lowlevel_new");
-    
-    // add the channel to the session
-    // this isn't strictly necessary, but let's do it anyways
-    fuse_session_add_chan(ctx->session, ctx->chan);
-    
-    // now, we can start listening for events on the channel
-    if ((ctx->ev = event_new(evbase, fuse_chan_fd(ctx->chan), EV_READ, &_evfuse_ev_read, ctx)) == NULL)
-        ERROR("event_new");
-
-    if (event_add(ctx->ev, NULL))
-        PERROR("event_add");
-
-    // and then we wait
-    return ctx;
-
-error:
-    evfuse_free(ctx);
-
-    return NULL;
-}
-
-void evfuse_close (struct evfuse *ctx) {
-    if (ctx->ev) {
-        // remove our event
-        if (event_del(ctx->ev))
-            PWARNING("event_del");
-
-        ctx->ev = NULL;
-    }
-
-    if (ctx->session) {
-        // remove the chan
-        fuse_session_remove_chan(ctx->chan);
-
-        // destroy the session
-        fuse_session_destroy(ctx->session);
-
-        ctx->session = NULL;
-    }
-
-    if (ctx->chan) {
-        // unmount
-        fuse_unmount(ctx->mountpoint, ctx->chan);
-
-        ctx->chan = NULL;
-    }
-
-    // free
-    free(ctx->recv_buf); ctx->recv_buf = NULL;
-    free(ctx->mountpoint); ctx->mountpoint = NULL;
-}
-
-void evfuse_free (struct evfuse *ctx) {
-    if (ctx) {
-        evfuse_close(ctx);
-
-        free(ctx);
-    }
-}
-
--- a/src/evfuse.h	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +0,0 @@
-#ifndef EVFUSE_H
-#define EVFUSE_H
-
-#define FUSE_USE_VERSION 26
-
-#include <event2/event.h>
-#include <fuse/fuse_lowlevel.h>
-
-/*
- * A wrapper for the fuse + libevent context
- */
-struct evfuse;
-
-/*
- * Create a new new evfuse context.
- */
-struct evfuse *evfuse_new (struct event_base *evbase, struct fuse_args *args, struct fuse_lowlevel_ops *llops, void *cb_data);
-
-/*
- * Close and free evfuse context.
- *
- * Safe to call after errors/llops.destroy
- */
-void evfuse_free (struct evfuse *ctx);
-
-#endif /* EVFUSE_H */
-
--- a/src/evsql.h	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,813 +0,0 @@
-#ifndef EVSQL_H
-#define EVSQL_H
-
-/**
- * @file src/evsql.h
- *
- * A SQL library designed for use with libevent and PostgreSQL's libpq. Provides support for queueing non-transactional
- * requests, transaction support, parametrized queries and result iteration.
- *
- * Currently, the API does not expose the underlying libpq data structures, but since it is currently the only
- * underlying implementation, there is no guarantee that the same API will actually work with other databases' interface
- * libraries...
- *
- * The order of function calls and callbacks goes something like this:
- *
- *  -   evsql_new_pq()
- *
- *  -   evsql_trans()
- *      -   evsql_trans_abort()
- *      -   evsql_trans_error_cb()
- *      -   evsql_trans_ready_cb()
- *
- *  -   evsql_query(), \ref evsql_param_ + evsql_query_params(), evsql_query_exec()
- *      -   evsql_query_abort()
- *      -   evsql_query_cb()
- *          -   \ref evsql_result_
- *          -   evsql_result_free()
- *
- *  -   evsql_trans_commit()
- *      -   evsql_trans_done_cb()
- *
- */
-
-/**
- * System includes
- */
-#include <stdint.h>
-#include <stdbool.h>
-#include <event2/event.h>
-
-/**
- * For err_t.
- */
-#include "lib/err.h"
-
-/**
- * @struct evsql
- *
- * The generic session handle used to manage a single "database connector" with multiple queries/transactions.
- *
- * @see \ref evsql_
- */
-struct evsql;
-
-/**
- * @struct evsql_trans
- *
- * Opaque transaction handle returned by evsql_trans() and used for the \ref evsql_query_ functions
- *
- * @see \ref evsql_trans_
- */
-struct evsql_trans;
-
-/**
- * @struct evsql_query
- *
- * Opaque query handle returned by the \ref evsql_query_ functions and used for evsql_query_abort()
- *
- * @see \ref evsql_query_
- */
-struct evsql_query;
-
-/**
- * @struct evsql_result
- *
- * Opaque result handle received by evsql_query_cb(), and used with the \ref evsql_result_ functions
- *
- * @see evsql_query_cb
- * @see \ref evsql_result_
- */
-struct evsql_result;
-
-/**
- * Various transaction isolation levels for conveniance
- *
- * @see evsql_trans
- */
-enum evsql_trans_type {
-    EVSQL_TRANS_DEFAULT,
-    EVSQL_TRANS_SERIALIZABLE,
-    EVSQL_TRANS_REPEATABLE_READ,
-    EVSQL_TRANS_READ_COMMITTED,
-    EVSQL_TRANS_READ_UNCOMMITTED,
-};
-
-/**
- * An item can be in different formats, the classical text-based format (i.e. snprintf "1234") or a more low-level
- * binary format (i.e uint16_t 0x04F9 in network-byte order).
- */
-enum evsql_item_format {
-    /** Format values as text strings */
-    EVSQL_FMT_TEXT,
-
-    /** Type-specific binary encoding */
-    EVSQL_FMT_BINARY,
-};
-
-/**
- * An item has a specific type, these correspond somewhat to the native database types.
- */
-enum evsql_item_type {
-    /** End marker, zero */
-    EVSQL_TYPE_INVALID,
-    
-    /** A SQL NULL */
-    EVSQL_TYPE_NULL_,
-    
-    /** A `struct evsql_item_binary` */
-    EVSQL_TYPE_BINARY,
-
-    /** A NUL-terminated char* */
-    EVSQL_TYPE_STRING,
-
-    /** A uint16_t value */
-    EVSQL_TYPE_UINT16,
-
-    /** A uint32_t value */
-    EVSQL_TYPE_UINT32,
-
-    /** A uint64_t value */
-    EVSQL_TYPE_UINT64,
-
-    EVSQL_TYPE_MAX
-};
-
-/**
- * Value for use with EVSQL_TYPE_BINARY, this just a non-NUL-terminated char* and an explicit length
- */
-struct evsql_item_binary {
-    /** The binary data */
-    const char *ptr;
-
-    /** Number of bytes pointed to by ptr */
-    size_t len;
-};
-
-/**
- * Metadata about the format and type of an item, this does not hold any actual value.
- */
-struct evsql_item_info {
-    /** The format */
-    enum evsql_item_format format;
-    
-    /** The type type */
-    enum evsql_item_type type;
-
-    /** Various flags */
-    struct evsql_item_flags {
-        /** The value may be NULL @see evsql_result_next */
-        bool null_ok : 1;
-    } flags;
-};
-
-/**
- * An union to provide storage for the values of small types
- *
- * @see evsql_item
- */
-union evsql_item_value {
-    /** 16-bit unsigned integer */
-    uint16_t uint16;
-
-    /** 32-bit unsigned integer */
-    uint32_t uint32;
-
-    /** 64-bit unsigned integer */
-    uint64_t uint64;
-};
-
-/**
- * A generic structure containing the type and value of a query parameter or a result field.
- *
- * @see evsql_query_info
- * @see evsql_query_params
- * @see evsql_result_info
- */
-struct evsql_item {
-    /** The "header" containing the type and format */
-    struct evsql_item_info info;
-
-    /**
-     * Pointer to the raw databytes. 
-     * Set to NULL for SQL NULLs, otherwise &value or an external buf 
-     */
-    const char *bytes;
-
-    /**
-     * Size of the byte array pointed to by bytes, zero for EVSQL_FMT_TEXT data.
-     */
-    size_t length;
-
-    /**
-     * Inline storage for small values
-     */
-    union evsql_item_value value;
-    
-    /** Internal flags */
-    struct {
-        /**
-         * The item has a value stored in `value`
-         */
-        bool has_value : 1;
-    } flags;
-};
-
-/**
- * Query meta-info, similar to a prepared statement.
- *
- * Contains the literal SQL query and the types of the parameters, but no more.
- *
- * @see evsql_query_exec
- */
-struct evsql_query_info {
-    /** The SQL query itself */
-    const char *sql;
-
-    /** 
-     * A variable-length array of the item_info parameters, terminated by an EVSQL_TYPE_INVALID entry.
-     */
-    struct evsql_item_info params[];
-};
-
-/**
- * Contains the query parameter types and their actual values
- *
- * @see evsql_query_params
- */
-struct evsql_query_params {
-    /** Requested result format for this query. XXX: move elsewhere */
-    enum evsql_item_format result_format;
-    
-    /**
-     * A variable-length array of the item parameter-values, terminated by an EVSQL_TYPE_INVALID entry.
-     */
-    struct evsql_item list[];
-};
-
-/**
- * Result layout metadata. This contains the stucture needed to decode result rows.
- *
- * @see evsql_result_begin
- */
-struct evsql_result_info {
-    /** XXX: make up something useful to stick here */
-    int _unused;
-
-    /**
-     * A variable-length array of the item_info column types.
-     */
-    struct evsql_item_info columns[];
-};
-
-/**
- * Magic macros for defining param/result info -lists
- *  
- * @code
- *  static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) {
- *      EVSQL_PARAM( UINT32 ),
- *      ...,
- *
- *      EVSQL_PARAMS_END
- *  };
- * @endcode
- *
- * @name EVSQL_TYPE/PARAM_*
- * @{
- */
-
-/**
- * A `struct evsql_item_info` initializer, using FMT_BINARY and the given EVSQL_TYPE_ -suffix.
- *
- * @param typenam the suffix of an evsql_item_type name
- *
- * @see struct evsql_item_info
- * @see enum evsql_item_type
- */
-#define EVSQL_TYPE(typenam)                     { EVSQL_FMT_BINARY, EVSQL_TYPE_ ## typenam  }
-
-/**
- * End marker for a `struct evsql_item_info` array.
- *
- * @see struct evsql_item_info
- */
-#define EVSQL_TYPE_END                          { EVSQL_FMT_BINARY, EVSQL_TYPE_INVALID      }
-
-/**
- * Initializer block for an evsql_query_params struct
- */
-#define EVSQL_PARAMS(result_fmt)            { result_fmt, 
-
-/**
- * An evsql_item initializer
- */
-#define EVSQL_PARAM(typenam)                    { EVSQL_TYPE(typenam) }
-
-/**
- * Include the ending item and terminate the pseudo-block started using #EVSQL_PARAMS
- */
-#define EVSQL_PARAMS_END                        { EVSQL_TYPE_END } \
-                                              } // <<<
-
-// @}
-
-/**
- * Callback definitions
- *
- * @name evsql_*_cb
- * @{
- */
-
-/**
- * Callback for handling query results.
- *
- * The query has completed, either succesfully or unsuccesfully.
- *
- * Use the \ref evsql_result_ functions to manipulate the results, and call evsql_result_free() (or equivalent) once done.
- *
- * @param res The result handle that must be result_free'd after use
- * @param arg The void* passed to \ref evsql_query_
- *
- * @see evsql_query
- */
-typedef void (*evsql_query_cb)(struct evsql_result *res, void *arg);
-
-/**
- * Callback for handling global-level errors.
- *
- * The evsql is not useable anymore.
- *
- * XXX: this is not actually called yet, as no retry logic is implemented, so an evsql itself never fails.
- *
- * @see evsql_new_pq
- */
-typedef void (*evsql_error_cb)(struct evsql *evsql, void *arg);
-
-/**
- * Callback for handling transaction-level errors. This may be called at any time during a transaction's lifetime,
- * including from within the \ref evsql_query_ functions (but not always).
- *
- * The transaction is not useable anymore.
- *
- * @param trans the transaction in question
- * @param arg the void* passed to evsql_trans
- *
- * @see evsql_trans
- */
-typedef void (*evsql_trans_error_cb)(struct evsql_trans *trans, void *arg);
-
-/**
- * Callback for handling evsql_trans/evsql_query_abort completion. The transaction is ready for use with \ref evsql_query_.
- *
- * @param trans the transaction in question
- * @param arg the void* passed to evsql_trans
- *
- * @see evsql_trans
- * @see evsql_query_abort
- */
-typedef void (*evsql_trans_ready_cb)(struct evsql_trans *trans, void *arg);
-
-/**
- * Callback for handling evsql_trans_commit completion. The transaction was commited, and should not be used anymore.
- *
- * @param trans the transaction in question
- * @param arg the void* passed to evsql_trans
- *
- * @see evsql_trans
- * @see evsql_trans_commit
- */
-typedef void (*evsql_trans_done_cb)(struct evsql_trans *trans, void *arg);
-
-// @}
-
-/**
- * Session functions
- *
- * @defgroup evsql_* Session interface
- * @see evsql.h
- * @{
- */
-
-/**
- * Session creation functions
- *
- * @defgroup evsql_new_* Session creation interface
- * @see evsql.h
- * @{
- */
-
-/**
- * Create a new PostgreSQL/libpq (evpq) -based evsql using the given conninfo.
- *
- * The given \a pq_conninfo pointer must stay valid for the duration of the evsql's lifetime.
- *
- * See the libpq reference manual for the syntax of pq_conninfo
- *
- * @param ev_base the libevent base to use
- * @param pq_conninfo the libpq connection information
- * @param error_fn XXX: not used, may be NULL
- * @param cb_arg: XXX: not used, argument for error_fn
- * @return the evsql context handle for use with other functions
- */
-struct evsql *evsql_new_pq (struct event_base *ev_base, const char *pq_conninfo, 
-    evsql_error_cb error_fn, 
-    void *cb_arg
-);
-
-// @}
-
-/**
- * Close a connection. Callbacks for waiting queries will not be run.
- *
- * XXX: not implemented yet.
- *
- * @param evsql the context handle from \ref evsql_new_
- */
-void evsql_close (struct evsql *evsql);
-
-// @}
-
-/**
- * Query API
- *
- * @defgroup evsql_query_* Query interface
- * @see evsql.h
- * @{
- */
-
-/**
- * Queue the given query for execution.
- *
- * If \a trans is given (i.e. not NULL), then the transaction must be idle, and the query will be executed in that
- * transaction's context. Otherwise, the query will be executed without a transaction using an idle connection, or
- * enqueued for later execution.
- *
- * Once the query is complete (got a result, got an error, the connection failed), then \a query_fn will be called.
- * The callback can use the \ref evsql_result_ functions to manipulate the query results.
- *
- * The returned evsql_query handle can be passed to evsql_query_abort at any point before \a query_fn being called. 
- *
- * @param evsql the context handle from \ref evsql_new_
- * @param trans the optional transaction handle from evsql_trans
- * @param command the raw SQL command itself
- * @param query_fn the evsql_query_cb() to call once the query is complete
- * @param cb_arg the void* passed to the above
- * @return the evsql_query handle that can be used to abort the query
- */
-struct evsql_query *evsql_query (struct evsql *evsql, struct evsql_trans *trans, const char *command, evsql_query_cb query_fn, void *cb_arg);
-
-/**
- * Execute the given SQL query using the list of parameter types/values given via evsql_query_params.
- *
- * Use the EVSQL_PARAMS macros to declare \a params, and the \ref evsql_param_ functions to populate the values.
- *
- * See evsql_query() for more info about behaviour.
- *
- * See the <a href="http://www.postgresql.org/docs/8.3/static/libpq-exec.html#LIBPQ-EXEC-MAIN">libpq PQexecParams tip</a>
- * for the parameter syntax to use.
- *
- * @param evsql the context handle from \ref evsql_new_
- * @param trans the optional transaction handle from evsql_trans
- * @param command the SQL command to bind the parameters to
- * @param params the parameter types and values
- * @param query_fn the evsql_query_cb() to call once the query is complete
- * @param cb_arg the void* passed to the above
- * @see evsql_query
- */
-struct evsql_query *evsql_query_params (struct evsql *evsql, struct evsql_trans *trans, 
-    const char *command, const struct evsql_query_params *params, 
-    evsql_query_cb query_fn, void *cb_arg
-);
-
-/**
- * Execute the given \a query_info's SQL query with the values given as variable arguments, using the \a query_info to
- * resolve the types.
- *
- * See evsql_query() for more info about behaviour.
- *
- * @param evsql the context handle from \ref evsql_new_
- * @param trans the optional transaction handle from evsql_trans
- * @param query_info the SQL query information
- * @param query_fn the evsql_query_cb() to call once the query is complete
- * @param cb_arg the void* passed to the above
- * @see evsql_query
- */
-struct evsql_query *evsql_query_exec (struct evsql *evsql, struct evsql_trans *trans, 
-    const struct evsql_query_info *query_info,
-    evsql_query_cb query_fn, void *cb_arg,
-    ...
-);
-
-/**
- * Abort a \a query returned by \ref evsql_query_ that has not yet completed (query_fn has not been called yet).
- *
- * The actual query itself may or may not be aborted (and hence may or may not be executed on the server), but \a query_fn
- * will not be called anymore, and the query will dispose of itself and any results returned.
- *
- * If the \a query is part of a transaction, then \a trans must be given, and the query must be the query that is currently
- * executing on that trans. The transaction's \a ready_fn will be called once the query has been aborted and the
- * transaction is now idle again.
- *
- * @param trans if the query is part of a transaction, then it MUST be given here
- * @param query the in-progress query to abort
- */
-void evsql_query_abort (struct evsql_trans *trans, struct evsql_query *query);
-
-/**
- * Print out a textual dump of the given \a sql query and \a params using DEBUG
- *
- * @param sql the SQL query command
- * @param params the list of parameter types and values
- */
-void evsql_query_debug (const char *sql, const struct evsql_query_params *params);
-
-// @}
-
-/**
- * Transaction API
- *
- * @defgroup evsql_trans_* Transaction interface
- * @see evsql.h
- * @{
- */
-
-/**
- * Create a new transaction.
- *
- * A transaction will be allocated its own connection, and the "BEGIN TRANSACTION ..." query will be sent (use the
- * \a type argument to specify this). 
- *
- * Once the transaction has been opened, the given \a ready_fn will be triggered, and the transaction can then
- * be used (see \ref evsql_query_).
- *
- * If, at any point, the transaction-connection fails, any pending query will be forgotten (i.e. the query callback
- * will NOT be called), and the given \a error_fn will be called. Note that this includes some, but not all,
- * cases where \ref evsql_query_ returns an error.
- *
- * Once you are done with the transaction, call either evsql_trans_commit() or evsql_trans_abort().
- *
- * @param evsql the context handle from \ref evsql_new_
- * @param type the type of transaction to create
- * @param error_fn the evsql_trans_error_cb() to call if this transaction fails
- * @param ready_fn the evsql_trans_ready_cb() to call once this transaction is ready for use
- * @param done_fn the evsql_trans_done_cb() to call once this transaction has been commmited
- * @param cb_arg the void* to pass to the above
- * @return the evsql_trans handle for use with other functions
- */
-struct evsql_trans *evsql_trans (struct evsql *evsql, enum evsql_trans_type type, 
-    evsql_trans_error_cb error_fn, 
-    evsql_trans_ready_cb ready_fn, 
-    evsql_trans_done_cb done_fn, 
-    void *cb_arg
-);
-
-/**
- * Commit a transaction using "COMMIT TRANSACTION".
- *
- * The transaction must be idle, just like for evsql_query. Once the transaction has been commited, the transaction's
- * \a done_fn will be called, after which the transaction must not be used anymore.
- *
- * You cannot abort a COMMIT, calling trans_abort() on trans after a succesful trans_commit is an error.
- *
- * Note that \a done_fn will never be called directly, always indirectly via the event loop.
- *
- * @param trans the transaction handle from evsql_trans to commit
- * @see evsql_trans
- */
-int evsql_trans_commit (struct evsql_trans *trans);
-
-/**
- * Abort a transaction, using "ROLLBACK TRANSACTION".
- *
- * No more transaction callbacks will be called. If there was a query running, it will be aborted, and the transaction
- * then rollback'd.
- *
- * You cannot abort a COMMIT, calling trans_abort on \a trans after a call to trans_commit is an error.
- * 
- * Do not call evsql_trans_abort from within evsql_trans_error_cb()!
- *
- * @param trans the transaction from evsql_trans to abort
- * @see evsql_trans
- */
-void evsql_trans_abort (struct evsql_trans *trans);
-
-/** 
- * Retrieve the transaction-specific error message from the underlying engine.
- *
- * Intended to be called from evsql_trans_error_cb()
- */
-const char *evsql_trans_error (struct evsql_trans *trans);
-
-// @}
-
-/**
- * Parameter-building functions.
- *
- * These manipulate the value of the given parameter index.
- *
- * @defgroup evsql_param_* Parameter interface
- * @see evsql.h
- * @{
- */
-
-/**
- * Sets the value of the parameter at the given index
- *
- * @param params the evsql_query_params struct
- * @param param the parameter index
- * @param ptr pointer to the binary data
- * @param len size of the binary data in bytes
- * @return zero on success, <0 on error
- */
-int evsql_param_binary (struct evsql_query_params *params, size_t param, const char *ptr, size_t len);
-
-/** @see evsql_param_binary */
-int evsql_param_string (struct evsql_query_params *params, size_t param, const char *ptr);
-
-/** @see evsql_param_binary */
-int evsql_param_uint16 (struct evsql_query_params *params, size_t param, uint16_t uval);
-
-/** @see evsql_param_binary */
-int evsql_param_uint32 (struct evsql_query_params *params, size_t param, uint32_t uval);
-
-/**
- * Sets the given parameter to NULL
- *
- * @param params the evsql_query_params struct
- * @param param the parameter index
- * @return zero on success, <0 on error
- */
-int evsql_param_null (struct evsql_query_params *params, size_t param);
-
-/**
- * Clears all the parameter values (sets them to NULL)
- *
- * @param params the evsql_query_params struct
- * @return zero on success, <0 on error
- */
-int evsql_params_clear (struct evsql_query_params *params);
-
-// @}
-
-/**
- * Result-handling functions
- *
- * @defgroup evsql_result_* Result interface
- * @see evsql.h
- * @see evsql_result
- * @{
- */
-
-/**
- * Check the result for errors. Intended for use with non-data queries, i.e. CREATE, etc.
- *
- * Returns zero if the query was OK, err otherwise. EIO indicates an SQL error, the error message can be retrived
- * using evsql_result_error.
- *
- * @param res the result handle passed to evsql_query_cb()
- * @return zero on success, EIO on SQL error, positive error code otherwise
- */
-err_t evsql_result_check (struct evsql_result *res);
-
-/**
- * The iterator-based interface results interface.
- *
- * Define an evsql_result_info struct that describes the columns returned by the query, and call evsql_result_begin on
- * the evsql_result. This verifies the query result, and then prepares it for iteration using evsql_result_next.
- *
- * Call evsql_result_end once you've stopped iteration.
- *
- * Returns zero if the evsql_result is ready for iteration, err otherwise. EIO indicates an SQL error, the error
- * message can be retreived using evsql_result_error. The result must be released in both cases.
- *
- * Note: currently the iterator state is simply stored in evsql_result, so only one iterator at a time per evsql_result.
- *
- * @param info the metadata to use to handle the result row columns
- * @param res the result handle passed to evsql_query_cb()
- * @return zero on success, +err on error
- */
-err_t evsql_result_begin (struct evsql_result_info *info, struct evsql_result *res);
-
-/**
- * Reads the next result row from the result prepared using evsql_result_begin. Stores the field values into to given
- * pointer arguments based on the evsql_result_info given to evsql_result_begin.
- *
- * If a field is NULL, and the result_info's evsql_item_type has flags.null_ok set, the given pointer is left
- * untouched, otherwise, an error is returned.
- *
- * @param res the result handle previous prepared using evsql_result_begin
- * @param ... a set of pointers corresponding to the evsql_result_info specified using evsql_result_begin
- * @return >0 when a row was read, zero when there are no more rows left, and -err on error
- */
-int evsql_result_next (struct evsql_result *res, ...);
-
-/**
- * Ends the result iteration, releasing any associated resources and the result itself.
- *
- * The result should not be iterated or accessed anymore.
- *
- * Note: this does the same thing as evsql_result_free, and works regardless of evsql_result_begin returning
- * succesfully or not.
- *
- * @param res the result handle passed to evsql_query_cb()
- * @see evsql_result_free
- */
-void evsql_result_end (struct evsql_result *res);
-
-/**
- * Get the error message associated with the result, intended for use after evsql_result_check/begin return an error
- * code.
- * 
- * @param res the result handle passed to evsql_query_cb()
- * @return a char* containing the NUL-terminated error string. Valid until evsql_result_free is called.
- */
-const char *evsql_result_error (const struct evsql_result *res);
-
-/**
- * Get the number of data rows returned by the query
- *
- * @param res the result handle passed to evsql_query_cb()
- * @return the number of rows, >= 0
- */
-size_t evsql_result_rows (const struct evsql_result *res);
-
-/**
- * Get the number of columns in the data results from the query
- *
- * @param res the result handle passed to evsql_query_cb()
- * @return the number of columns, presumeably zero if there were no results
- */
-size_t evsql_result_cols (const struct evsql_result *res);
-
-/**
- * Get the number of rows affected by an UPDATE/INSERT/etc query.
- *
- * @param res the result handle passed to evsql_query_cb()
- * @return the number of rows affected, >= 0
- */
-size_t evsql_result_affected (const struct evsql_result *res);
-
-/**
- * Fetch the raw binary value for the given field, returning it via ptr/size.
- *
- * The given row/col must be within bounds as returned by evsql_result_rows/cols.
- *
- * *ptr will point to *size bytes of read-only memory allocated internally.
- *
- * @param res the result handle passed to evsql_query_cb()
- * @param row the row index to access
- * @param col the column index to access
- * @param ptr where to store a pointer to the read-only field data, free'd upon evsql_result_free
- * @param size updated to the size of the field value pointed to by ptr
- * @param nullok when true and the field value is NULL, *ptr and *size are not modified, otherwise NULL means an error
- * @return zero on success, <0 on error
- */
-int evsql_result_binary (const struct evsql_result *res, size_t row, size_t col, const char **ptr, size_t *size, bool nullok);
-
-/**
- * Fetch the textual value of the given field, returning it via ptr.
- *
- * The given row/col must be within bounds as returned by evsql_result_rows/cols.
- *
- * *ptr will point to a NUL-terminated string allocated internally.
- *
- * @param res the result handle passed to evsql_query_cb()
- * @param row the row index to access
- * @param col the column index to access
- * @param ptr where to store a pointer to the read-only field data, free'd upon evsql_result_free
- * @param nullok when true and the field value is NULL, *ptr and *size are not modified, otherwise NULL means an error
- * @return zero on success, <0 on error
- */
-int evsql_result_string (const struct evsql_result *res, size_t row, size_t col, const char **ptr, int nullok);
-
-/**
- * Use evsql_result_binary to read a binary field value, and then convert it using ntoh[slq], storing the value in
- * *val.
- *
- * The given row/col must be within bounds as returned by evsql_result_rows/cols.
- *
- * @param res the result handle passed to evsql_query_cb()
- * @param row the row index to access
- * @param col the column index to access
- * @param uval where to store the decoded value
- * @param nullok when true and the field value is NULL, *ptr and *size are not modified, otherwise NULL means an error
- * @return zero on success, <0 on error
- */
-int evsql_result_uint16 (const struct evsql_result *res, size_t row, size_t col, uint16_t *uval, int nullok);
-
-/** @see evsql_result_uint16 */
-int evsql_result_uint32 (const struct evsql_result *res, size_t row, size_t col, uint32_t *uval, int nullok);
-
-/** @see evsql_result_uint16 */
-int evsql_result_uint64 (const struct evsql_result *res, size_t row, size_t col, uint64_t *uval, int nullok);
-
-/**
- * Every result handle passed to evsql_query_cb() MUST be released by the user, using this function.
- *
- * @param res the result handle passed to evsql_query_cb()
- */
-void evsql_result_free (struct evsql_result *res);
-
-// @}
-
-#endif /* EVSQL_H */
--- a/src/evsql/evsql.c	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,915 +0,0 @@
-#define _GNU_SOURCE
-#include <stdlib.h>
-#include <assert.h>
-#include <string.h>
-
-#include "evsql.h"
-#include "../lib/log.h"
-#include "../lib/error.h"
-#include "../lib/misc.h"
-
-/*
- * A couple function prototypes
- */ 
-static void _evsql_pump (struct evsql *evsql, struct evsql_conn *conn);
-
-/*
- * Actually execute the given query.
- *
- * The backend should be able to accept the query at this time.
- *
- * You should assume that if trying to execute a query fails, then the connection should also be considred as failed.
- */
-static int _evsql_query_exec (struct evsql_conn *conn, struct evsql_query *query, const char *command) {
-    int err;
-
-    DEBUG("evsql.%p: exec query=%p on trans=%p on conn=%p:", conn->evsql, query, conn->trans, conn);
-
-    switch (conn->evsql->type) {
-        case EVSQL_EVPQ:
-            // got params?
-            if (query->params.count) {
-                err = evpq_query_params(conn->engine.evpq, command,
-                    query->params.count, 
-                    query->params.types, 
-                    query->params.values, 
-                    query->params.lengths, 
-                    query->params.formats, 
-                    query->params.result_format
-                );
-
-            } else {
-                // plain 'ole query
-                err = evpq_query(conn->engine.evpq, command);
-            }
-
-            if (err) {
-                if (PQstatus(evpq_pgconn(conn->engine.evpq)) != CONNECTION_OK)
-                    WARNING("conn failed");
-                else
-                    WARNING("query failed, dropping conn as well");
-            }
-
-            break;
-        
-        default:
-            FATAL("evsql->type");
-    }
-
-    if (!err)
-        // assign the query
-        conn->query = query;
-
-    return err;
-}
-
-void _evsql_query_free (struct evsql_query *query) {
-    if (!query)
-        return;
-        
-    assert(query->command == NULL);
-    
-    // free params if present
-    free(query->params.types);
-    free(query->params.values);
-    free(query->params.lengths);
-    free(query->params.formats);
-
-    // free the query itself
-    free(query);
-}
-
-/*
- * Execute the callback if res is given, and free the query.
- *
- * The query has been aborted, it will simply be freed
- */
-static void _evsql_query_done (struct evsql_query *query, struct evsql_result *res) {
-    if (res) {
-        if (query->cb_fn)
-            // call the callback
-            query->cb_fn(res, query->cb_arg);
-        else {
-            WARNING("supressing cb_fn because query was aborted");
-            
-            // free the results
-            evsql_result_free(res);
-        }
-    }
-
-    // free
-    _evsql_query_free(query);
-}
-
-/*
- * XXX:
- * /
-static void _evsql_destroy (struct evsql *evsql, const struct evsql_result *res) {
-    struct evsql_query *query;
-    
-    // clear the queue
-    while ((query = TAILQ_FIRST(&evsql->query_queue)) != NULL) {
-        _evsql_query_done(query, res);
-        
-        TAILQ_REMOVE(&evsql->query_queue, query, entry);
-    }
-    
-    // free
-    free(evsql);
-}
-*/
-
-/*
- * Free the transaction, it should already be deassociated from the query and conn.
- */
-static void _evsql_trans_free (struct evsql_trans *trans) {
-    // ensure we don't leak anything
-    assert(trans->query == NULL);
-    assert(trans->conn == NULL);
-    
-    // free
-    free(trans);
-}
-
-/*
- * Release a connection. It should already be deassociated from the trans and query.
- *
- * Releases the engine, removes from the conn_list and frees this.
- */
-static void _evsql_conn_release (struct evsql_conn *conn) {
-    // ensure we don't leak anything
-    assert(conn->trans == NULL);
-    assert(conn->query == NULL);
-
-    // release the engine
-    switch (conn->evsql->type) {
-        case EVSQL_EVPQ:
-            evpq_release(conn->engine.evpq);
-            break;
-        
-        default:
-            FATAL("evsql->type");
-    }
-    
-    // remove from list
-    LIST_REMOVE(conn, entry);
-    
-    // catch deadlocks
-    assert(!LIST_EMPTY(&conn->evsql->conn_list) || TAILQ_EMPTY(&conn->evsql->query_queue));
-
-    // free
-    free(conn);
-}
-
-/*
- * Release a transaction, it should already be deassociated from the query.
- *
- * Perform a two-way-deassociation with the conn, and then free the trans.
- */
-static void _evsql_trans_release (struct evsql_trans *trans) {
-    assert(trans->query == NULL);
-    assert(trans->conn != NULL);
-
-    // deassociate the conn
-    trans->conn->trans = NULL; trans->conn = NULL;
-
-    // free the trans
-    _evsql_trans_free(trans);
-}
-
-/*
- * Fail a single query, this will trigger the callback and free it.
- *
- * NOTE: Only for *TRANSACTIONLESS* queries.
- */
-static void _evsql_query_fail (struct evsql* evsql, struct evsql_query *query) {
-    struct evsql_result res; ZINIT(res);
-    
-    // set up the result_info
-    res.evsql = evsql;
-    res.error = 1;
-    
-    // finish off the query
-    _evsql_query_done(query, &res);
-}
-
-/*
- * Fail a transaction, this will silently drop any query, trigger the error callback, two-way-deassociate/release the
- * conn, and then free the trans.
- */ 
-static void _evsql_trans_fail (struct evsql_trans *trans) {
-    if (trans->query) {
-        // free the query silently
-        _evsql_query_free(trans->query); trans->query = NULL;
-
-        // also deassociate it from the conn!
-        trans->conn->query = NULL;
-    }
-
-    // tell the user
-    // XXX: trans is in a bad state during this call
-    if (trans->error_fn)
-        trans->error_fn(trans, trans->cb_arg);
-    else
-        WARNING("supressing error because error_fn was NULL");
- 
-    // deassociate and release the conn
-    trans->conn->trans = NULL; _evsql_conn_release(trans->conn); trans->conn = NULL;
-
-    // pump the queue for requests that were waiting for this connection
-    _evsql_pump(trans->evsql, NULL);
-
-    // free the trans
-    _evsql_trans_free(trans);
-}
-
-/*
- * Fail a connection. If the connection is transactional, this will just call _evsql_trans_fail, but otherwise it will
- * fail any ongoing query, and then release the connection.
- */
-static void _evsql_conn_fail (struct evsql_conn *conn) {
-    if (conn->trans) {
-        // let transactions handle their connection failures
-        _evsql_trans_fail(conn->trans);
-
-    } else {
-        if (conn->query) {
-            // fail the in-progress query
-            _evsql_query_fail(conn->evsql, conn->query); conn->query = NULL;
-        }
-
-        // finish off the whole connection
-        _evsql_conn_release(conn);
-    }
-}
-
-/*
- * Processes enqueued non-transactional queries until the queue is empty, or we managed to exec a query.
- *
- * If execing a query on a connection fails, both the query and the connection are failed (in that order).
- *
- * Any further queries will then also be failed, because there's no reconnection/retry logic yet.
- *
- * This means that if conn is NULL, all queries are failed.
- */
-static void _evsql_pump (struct evsql *evsql, struct evsql_conn *conn) {
-    struct evsql_query *query;
-    int err;
-    
-    // look for waiting queries
-    while ((query = TAILQ_FIRST(&evsql->query_queue)) != NULL) {
-        // zero err
-        err = 0;
-
-        // dequeue
-        TAILQ_REMOVE(&evsql->query_queue, query, entry);
-        
-        if (conn) {
-            // try and execute it
-            err = _evsql_query_exec(conn, query, query->command);
-        }
-
-        // free the command buf
-        free(query->command); query->command = NULL;
-
-        if (err || !conn) {
-            if (!conn) {
-                // warn when dropping queries
-                WARNING("failing query becuse there are no conns");
-            }
-
-            // fail the query
-            _evsql_query_fail(evsql, query);
-            
-            if (conn) {
-                // fail the connection
-                WARNING("failing the connection because a query-exec failed");
-
-                _evsql_conn_fail(conn); conn = NULL;
-            }
-
-        } else {
-            // we have succesfully enqueued a query, and we can wait for this connection to complete
-            break;
-
-        }
-
-        // handle the rest of the queue
-    }
-    
-    // ok
-    return;
-}
-
-/*
- * Callback for a trans's 'BEGIN' query, which means the transaction is now ready for use.
- */
-static void _evsql_trans_ready (struct evsql_result *res, void *arg) {
-    struct evsql_trans *trans = arg;
-
-    assert(trans != NULL);
-
-    // check for errors
-    if (res->error)
-        ERROR("transaction 'BEGIN' failed: %s", evsql_result_error(res));
-    
-    // transaction is now ready for use
-    trans->ready_fn(trans, trans->cb_arg);
-    
-    // good
-    return;
-
-error:
-    _evsql_trans_fail(trans);
-}
-
-/*
- * The transaction's connection is ready, send the 'BEGIN' query.
- *
- * If anything fails, calls _evsql_trans_fail and returns nonzero, zero on success
- */
-static int _evsql_trans_conn_ready (struct evsql *evsql, struct evsql_trans *trans) {
-    char trans_sql[EVSQL_QUERY_BEGIN_BUF];
-    const char *isolation_level;
-    int ret;
-    
-    // determine the isolation_level to use
-    switch (trans->type) {
-        case EVSQL_TRANS_DEFAULT:
-            isolation_level = NULL; break;
-
-        case EVSQL_TRANS_SERIALIZABLE:
-            isolation_level = "SERIALIZABLE"; break;
-
-        case EVSQL_TRANS_REPEATABLE_READ:
-            isolation_level = "REPEATABLE READ"; break;
-
-        case EVSQL_TRANS_READ_COMMITTED:
-            isolation_level = "READ COMMITTED"; break;
-
-        case EVSQL_TRANS_READ_UNCOMMITTED:
-            isolation_level = "READ UNCOMMITTED"; break;
-
-        default:
-            FATAL("trans->type: %d", trans->type);
-    }
-    
-    // build the trans_sql
-    if (isolation_level)
-        ret = snprintf(trans_sql, EVSQL_QUERY_BEGIN_BUF, "BEGIN TRANSACTION ISOLATION LEVEL %s", isolation_level);
-    else
-        ret = snprintf(trans_sql, EVSQL_QUERY_BEGIN_BUF, "BEGIN TRANSACTION");
-    
-    // make sure it wasn't truncated
-    if (ret >= EVSQL_QUERY_BEGIN_BUF)
-        ERROR("trans_sql overflow: %d >= %d", ret, EVSQL_QUERY_BEGIN_BUF);
-    
-    // execute the query
-    if (evsql_query(evsql, trans, trans_sql, _evsql_trans_ready, trans) == NULL)
-        ERROR("evsql_query");
-    
-    // success
-    return 0;
-
-error:
-    // fail the transaction
-    _evsql_trans_fail(trans);
-
-    return -1;
-}
-
-/*
- * The evpq connection was succesfully established.
- */ 
-static void _evsql_evpq_connected (struct evpq_conn *_conn, void *arg) {
-    struct evsql_conn *conn = arg;
-
-    if (conn->trans)
-        // notify the transaction
-        // don't care about errors
-        (void) _evsql_trans_conn_ready(conn->evsql, conn->trans);
-    
-    else
-        // pump any waiting transactionless queries
-        _evsql_pump(conn->evsql, conn);
-}
-
-/*
- * Got one result on this evpq connection.
- */
-static void _evsql_evpq_result (struct evpq_conn *_conn, PGresult *result, void *arg) {
-    struct evsql_conn *conn = arg;
-    struct evsql_query *query = conn->query;
-
-    assert(query != NULL);
-
-    // if we get multiple results, only return the first one
-    if (query->result.pq) {
-        WARNING("[evsql] evpq query returned multiple results, discarding previous one");
-        
-        PQclear(query->result.pq); query->result.pq = NULL;
-    }
-    
-    // remember the result
-    query->result.pq = result;
-}
-
-/*
- * No more results for this query.
- */
-static void _evsql_evpq_done (struct evpq_conn *_conn, void *arg) {
-    struct evsql_conn *conn = arg;
-    struct evsql_query *query = conn->query;
-    struct evsql_result res; ZINIT(res);
-    
-    assert(query != NULL);
-    
-    // set up the result_info
-    res.evsql = conn->evsql;
-    res.result = query->result;
-    
-    if (query->result.pq == NULL) {
-        // if a query didn't return any results (bug?), warn and fail the query
-        WARNING("[evsql] evpq query didn't return any results");
-
-        res.error = 1;
-    
-    } else if (strcmp(PQresultErrorMessage(query->result.pq), "") != 0) {
-        // the query failed with some error
-        res.error = 1;
-
-    } else {
-        // the query succeeded \o/
-        res.error = 0;
-
-    }
-
-    // de-associate the query from the connection
-    conn->query = NULL;
-    
-    // how we handle query completion depends on if we're a transaction or not
-    if (conn->trans) {
-        // we can deassign the trans's query
-        conn->trans->query = NULL;
-
-        // was an abort?
-        if (!query->cb_fn)
-            // notify the user that the transaction query has been aborted
-            conn->trans->ready_fn(conn->trans, conn->trans->cb_arg);
-
-        // then hand the query to the user
-        _evsql_query_done(query, &res);
-        
-    } else {
-        // a transactionless query, so just finish it off and pump any other waiting ones
-        _evsql_query_done(query, &res);
-
-        // pump the next one
-        _evsql_pump(conn->evsql, conn);
-    }
-}
-
-/*
- * The connection failed.
- */
-static void _evsql_evpq_failure (struct evpq_conn *_conn, void *arg) {
-    struct evsql_conn *conn = arg;
-    
-    // just fail the conn
-    _evsql_conn_fail(conn);
-}
-
-/*
- * Our evpq behaviour
- */
-static struct evpq_callback_info _evsql_evpq_cb_info = {
-    .fn_connected       = _evsql_evpq_connected,
-    .fn_result          = _evsql_evpq_result,
-    .fn_done            = _evsql_evpq_done,
-    .fn_failure         = _evsql_evpq_failure,
-};
-
-/*
- * Allocate the generic evsql context.
- */
-static struct evsql *_evsql_new_base (struct event_base *ev_base, evsql_error_cb error_fn, void *cb_arg) {
-    struct evsql *evsql = NULL;
-    
-    // allocate it
-    if ((evsql = calloc(1, sizeof(*evsql))) == NULL)
-        ERROR("calloc");
-
-    // store
-    evsql->ev_base = ev_base;
-    evsql->error_fn = error_fn;
-    evsql->cb_arg = cb_arg;
-
-    // init
-    LIST_INIT(&evsql->conn_list);
-    TAILQ_INIT(&evsql->query_queue);
-
-    // done
-    return evsql;
-
-error:
-    return NULL;
-}
-
-/*
- * Start a new connection and add it to the list, it won't be ready until _evsql_evpq_connected is called
- */
-static struct evsql_conn *_evsql_conn_new (struct evsql *evsql) {
-    struct evsql_conn *conn = NULL;
-    
-    // allocate
-    if ((conn = calloc(1, sizeof(*conn))) == NULL)
-        ERROR("calloc");
-
-    // init
-    conn->evsql = evsql;
-    
-    // connect the engine
-    switch (evsql->type) {
-        case EVSQL_EVPQ:
-            if ((conn->engine.evpq = evpq_connect(evsql->ev_base, evsql->engine_conf.evpq, _evsql_evpq_cb_info, conn)) == NULL)
-                goto error;
-            
-            break;
-            
-        default:
-            FATAL("evsql->type");
-    }
-
-    // add it to the list
-    LIST_INSERT_HEAD(&evsql->conn_list, conn, entry);
-
-    // success
-    return conn;
-
-error:
-    free(conn);
-
-    return NULL;
-}
-
-struct evsql *evsql_new_pq (struct event_base *ev_base, const char *pq_conninfo, evsql_error_cb error_fn, void *cb_arg) {
-    struct evsql *evsql = NULL;
-    
-    // base init
-    if ((evsql = _evsql_new_base (ev_base, error_fn, cb_arg)) == NULL)
-        goto error;
-
-    // store conf
-    evsql->engine_conf.evpq = pq_conninfo;
-
-    // pre-create one connection
-    if (_evsql_conn_new(evsql) == NULL)
-        goto error;
-
-    // done
-    return evsql;
-
-error:
-    // XXX: more complicated than this?
-    free(evsql); 
-
-    return NULL;
-}
-
-/*
- * Checks if the connection is already allocated for some other trans/query.
- *
- * Returns:
- *      0       connection idle, can be allocated
- *      >1      connection busy
- */
-static int _evsql_conn_busy (struct evsql_conn *conn) {
-    // transactions get the connection to themselves
-    if (conn->trans)
-        return 1;
-    
-    // if it has a query assigned, it's busy
-    if (conn->query)
-        return 1;
-
-    // otherwise, it's all idle
-    return 0;
-}
-
-/*
- * Checks if the connection is ready for use (i.e. _evsql_evpq_connected was called).
- *
- * The connection should not already have a query running.
- *
- * Returns 
- *  <0  the connection is not valid (failed, query in progress)
- *  0   the connection is still pending, and will become ready at some point
- *  >0  it's ready
- */
-static int _evsql_conn_ready (struct evsql_conn *conn) {
-    switch (conn->evsql->type) {
-        case EVSQL_EVPQ: {
-            enum evpq_state state = evpq_state(conn->engine.evpq);
-            
-            switch (state) {
-                case EVPQ_CONNECT:
-                    return 0;
-                
-                case EVPQ_CONNECTED:
-                    return 1;
-
-                case EVPQ_QUERY:
-                case EVPQ_INIT:
-                case EVPQ_FAILURE:
-                    return -1;
-                
-                default:
-                    FATAL("evpq_state: %d", state);
-            }
-
-        }
-        
-        default:
-            FATAL("evsql->type: %d", conn->evsql->type);
-    }
-}
-
-/*
- * Allocate a connection for use and return it via *conn_ptr, or if may_queue is nonzero and the connection pool is
- * getting full, return NULL (query should be queued).
- *
- * Note that the returned connection might not be ready for use yet (if we created a new one, see _evsql_conn_ready).
- *
- * Returns zero if a connection was found or the request should be queued, or nonzero if something failed and the
- * request should be dropped.
- */
-static int _evsql_conn_get (struct evsql *evsql, struct evsql_conn **conn_ptr, int may_queue) {
-    int have_nontrans = 0;
-    *conn_ptr = NULL;
-    
-    // find a connection that isn't busy and is ready (unless the query queue is empty).
-    LIST_FOREACH(*conn_ptr, &evsql->conn_list, entry) {
-        // we can only have a query enqueue itself if there is a non-trans conn it can later use
-        if (!(*conn_ptr)->trans)
-            have_nontrans = 1;
-
-        // skip busy conns always
-        if (_evsql_conn_busy(*conn_ptr))
-            continue;
-        
-        // accept pending conns as long as there are NO enqueued queries (might cause deadlock otherwise)
-        if (_evsql_conn_ready(*conn_ptr) == 0 && TAILQ_EMPTY(&evsql->query_queue))
-            break;
-
-        // accept conns that are in a fully ready state
-        if (_evsql_conn_ready(*conn_ptr) > 0)
-            break;
-    }
-    
-    // if we found an idle connection, we can just return that right away
-    if (*conn_ptr)
-        return 0;
-
-    // return NULL if may_queue and we have a non-trans conn that we can, at some point, use
-    if (may_queue && have_nontrans)
-        return 0;
-    
-    // we need to open a new connection
-    if ((*conn_ptr = _evsql_conn_new(evsql)) == NULL)
-        goto error;
-
-    // good
-    return 0;
-error:
-    return -1;
-}
-
-struct evsql_trans *evsql_trans (struct evsql *evsql, enum evsql_trans_type type, evsql_trans_error_cb error_fn, evsql_trans_ready_cb ready_fn, evsql_trans_done_cb done_fn, void *cb_arg) {
-    struct evsql_trans *trans = NULL;
-
-    // allocate it
-    if ((trans = calloc(1, sizeof(*trans))) == NULL)
-        ERROR("calloc");
-
-    // store
-    trans->evsql = evsql;
-    trans->ready_fn = ready_fn;
-    trans->done_fn = done_fn;
-    trans->cb_arg = cb_arg;
-    trans->type = type;
-
-    // find a connection
-    if (_evsql_conn_get(evsql, &trans->conn, 0))
-        ERROR("_evsql_conn_get");
-
-    // associate the conn
-    trans->conn->trans = trans;
-
-    // is it already ready?
-    if (_evsql_conn_ready(trans->conn) > 0) {
-        // call _evsql_trans_conn_ready directly, it will handle cleanup (silently, !error_fn)
-        if (_evsql_trans_conn_ready(evsql, trans)) {
-            // return NULL directly
-            return NULL;
-        }
-
-    } else {
-        // otherwise, wait for the conn to be ready
-         
-    }
-    
-    // and let it pass errors to the user
-    trans->error_fn = error_fn;
-
-    // ok
-    return trans;
-
-error:
-    free(trans);
-
-    return NULL;
-}
-
-/*
- * Internal query functions
- */
-struct evsql_query *_evsql_query_new (struct evsql *evsql, struct evsql_trans *trans, evsql_query_cb query_fn, void *cb_arg) {
-    struct evsql_query *query = NULL;
-    
-    // if it's part of a trans, then make sure the trans is idle
-    if (trans && trans->query)
-        ERROR("transaction is busy");
-
-    // allocate it
-    if ((query = calloc(1, sizeof(*query))) == NULL)
-        ERROR("calloc");
-
-    // store
-    query->cb_fn = query_fn;
-    query->cb_arg = cb_arg;
-
-    // success
-    return query;
-
-error:
-    return NULL;
-}
-
-int _evsql_query_enqueue (struct evsql *evsql, struct evsql_trans *trans, struct evsql_query *query, const char *command) {
-    // transaction queries are handled differently
-    if (trans) {
-        // it's an in-transaction query
-        assert(trans->query == NULL);
-        
-        // assign the query
-        trans->query = query;
-
-        // execute directly
-        if (_evsql_query_exec(trans->conn, query, command)) {
-            // ack, fail the transaction
-            _evsql_trans_fail(trans);
-            
-            // caller frees query
-            goto error;
-        }
-
-    } else {
-        struct evsql_conn *conn;
-        
-        // find an idle connection
-        if ((_evsql_conn_get(evsql, &conn, 1)))
-            ERROR("couldn't allocate a connection for the query");
-
-        // we must enqueue if no idle conn or the conn is not yet ready
-        if (conn && _evsql_conn_ready(conn) > 0) {
-            // execute directly
-            if (_evsql_query_exec(conn, query, command)) {
-                // ack, fail the connection
-                _evsql_conn_fail(conn);
-                
-                // make sure we don't deadlock any queries, but if this query got a conn directly, then we shouldn't
-                // have any queries enqueued anyways
-                assert(TAILQ_EMPTY(&evsql->query_queue));
-                
-                // caller frees query
-                goto error;
-            }
-
-        } else {
-            // copy the command for later execution
-            if ((query->command = strdup(command)) == NULL)
-                ERROR("strdup");
-            
-            // enqueue until some connection pumps the queue
-            TAILQ_INSERT_TAIL(&evsql->query_queue, query, entry);
-        }
-    }
-
-    // ok, good
-    return 0;
-
-error:
-    return -1;
-}
-
-
-void _evsql_trans_commit_res (struct evsql_result *res, void *arg) {
-    struct evsql_trans *trans = arg;
-
-    // check for errors
-    if (res->error)
-        ERROR("transaction 'COMMIT' failed: %s", evsql_result_error(res));
-    
-    // transaction is now done
-    trans->done_fn(trans, trans->cb_arg);
-    
-    // release it
-    _evsql_trans_release(trans);
-
-    // success
-    return;
-
-error:
-    _evsql_trans_fail(trans);
-}
-
-int evsql_trans_commit (struct evsql_trans *trans) {
-    static const char *sql = "COMMIT TRANSACTION";
-
-    if (trans->query)
-        ERROR("cannot COMMIT because transaction is still busy");
-    
-    // query
-    if (evsql_query(trans->evsql, trans, sql, _evsql_trans_commit_res, trans) == NULL)
-        goto error;
-    
-    // mark it as commited in case someone wants to abort it
-    trans->has_commit = 1;
-
-    // success
-    return 0;
-
-error:
-    return -1;
-}
-
-void _evsql_trans_rollback_res (struct evsql_result *res, void *arg) {
-    struct evsql_trans *trans = arg;
-
-    // fail the connection on errors
-    if (res->error)
-        ERROR("transaction 'ROLLBACK' failed: %s", evsql_result_error(res));
-
-    // release it
-    _evsql_trans_release(trans);
-
-    // success
-    return;
-
-error:
-    // fail the connection too, errors are supressed
-    _evsql_trans_fail(trans);
-}
-
-/*
- * Used as the ready_fn callback in case of abort, otherwise directly
- */
-void _evsql_trans_rollback (struct evsql_trans *trans, void *arg) {
-    static const char *sql = "ROLLBACK TRANSACTION";
-
-    (void) arg;
-
-    // query
-    if (evsql_query(trans->evsql, trans, sql, _evsql_trans_rollback_res, trans) == NULL) {
-        // fail the transaction/connection, errors are supressed
-        _evsql_trans_fail(trans);
-    }
-
-}
-
-void evsql_trans_abort (struct evsql_trans *trans) {
-    // supress errors
-    trans->error_fn = NULL;
-    
-    if (trans->has_commit) {
-        // abort after commit doesn't make sense
-        FATAL("transaction was already commited");
-    }
-
-    if (trans->query) {
-        // gah, some query is running
-        WARNING("aborting pending query");
-        
-        // prepare to rollback once complete by hijacking ready_fn
-        trans->ready_fn = _evsql_trans_rollback;
-        
-        // abort
-        evsql_query_abort(trans, trans->query);
-
-    } else {
-        // just rollback directly
-        _evsql_trans_rollback(trans, NULL);
-
-    }
-}
-
--- a/src/evsql/evsql.h	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,192 +0,0 @@
-#ifndef EVSQL_INTERNAL_H
-#define EVSQL_INTERNAL_H
-
-/*
- * Internal interfaces
- */
-
-#include <sys/queue.h>
-
-#include <event2/event.h>
-
-#include "../evsql.h"
-#include "../evpq.h"
-
-/*
- * The engine type
- */
-enum evsql_type {
-    EVSQL_EVPQ,     // evpq
-};
-
-/*
- * Contains the type, engine configuration, list of connections and waiting query queue.
- */
-struct evsql {
-    // what event_base to use
-    struct event_base *ev_base;
-
-    // what engine we use
-    enum evsql_type type;
-
-    // callbacks
-    evsql_error_cb error_fn;
-    void *cb_arg;
-    
-    // engine-specific connection configuration
-    union {
-        const char *evpq;
-    } engine_conf;
-
-    // list of connections that are open
-    LIST_HEAD(evsql_conn_list, evsql_conn) conn_list;
-   
-    // list of queries running or waiting to run
-    TAILQ_HEAD(evsql_query_queue, evsql_query) query_queue;
-};
-
-/*
- * A single connection to the server.
- *
- * Contains the engine connection, may have a transaction associated, and may have a query associated.
- */
-struct evsql_conn {
-    // evsql we belong to
-    struct evsql *evsql;
-
-    // engine-specific connection info
-    union {
-        struct evpq_conn *evpq;
-    } engine;
-
-    // our position in the conn list
-    LIST_ENTRY(evsql_conn) entry;
-
-    // are we running a transaction?
-    struct evsql_trans *trans;
-
-    // are we running a transactionless query?
-    struct evsql_query *query;
-};
-
-/*
- * A single transaction.
- *
- * Has a connection associated and possibly a query (which will also be associated with the connection)
- */
-struct evsql_trans {
-    // our evsql_conn/evsql
-    struct evsql *evsql;
-    struct evsql_conn *conn;
-    
-    // callbacks
-    evsql_trans_error_cb error_fn;
-    evsql_trans_ready_cb ready_fn;
-    evsql_trans_done_cb done_fn;
-    void *cb_arg;
-
-    // the transaction type
-    enum evsql_trans_type type;
-
-    // has evsql_trans_commit be called?
-    int has_commit : 1;
-
-    // our current query
-    struct evsql_query *query;
-
-};
-
-/*
- * Backend result handle
- */
-union evsql_result_handle {
-    PGresult *pq;
-};
-
-/*
- * A single query.
- *
- * Has the info needed to exec the query (as these may be queued), and the callback/result info.
- */
-struct evsql_query {
-    // the actual SQL query, this may or may not be ours, see _evsql_query_exec
-    char *command;
-    
-    // possible query params
-    struct evsql_query_params_pq {
-        int count;
-
-        Oid *types;
-        const char **values;
-        int *lengths;
-        int *formats;
-
-        // storage for numeric values
-        union evsql_item_value *item_vals;
-
-        int result_format;
-    } params;
-
-    // our callback
-    evsql_query_cb cb_fn;
-    void *cb_arg;
-        
-    // the result we get
-    union evsql_result_handle result;
-
-    // our position in the query list
-    TAILQ_ENTRY(evsql_query) entry;
-};
-
-// the result
-struct evsql_result {
-    struct evsql *evsql;
-
-    // possible error code
-    int error;
-    
-    // the actual result
-    union evsql_result_handle result;
-
-    // result_* state
-    struct evsql_result_info *info;
-    size_t row_offset;
-};
-
-
-// maximum length for a 'BEGIN TRANSACTION ...' query
-#define EVSQL_QUERY_BEGIN_BUF 512
-
-// the should the OID of some valid psql type... *ANY* valid psql type, doesn't matter, only used for NULLs
-// 16 = bool in 8.3
-#define EVSQL_PQ_ARBITRARY_TYPE_OID 16
-
-/*
- * Core query-submission interface.
- *
- * This performs some error-checking on the trans, allocates the evsql_query and does some basic initialization.
- *
- * This does not actually enqueue the query anywhere, no reference is stored anywhere.
- *
- * Returns the new evsql_query on success, NULL on failure.
- */
-struct evsql_query *_evsql_query_new (struct evsql *evsql, struct evsql_trans *trans, evsql_query_cb query_fn, void *cb_arg);
-
-/*
- * Begin processing the given query, which should now be fully filled out.
- *
- * If trans is given, it MUST be idle, and the query will be executed. Otherwise, it will either be executed directly
- * or enqueued for future execution.
- *
- * Returns zero on success, nonzero on failure.
- */
-int _evsql_query_enqueue (struct evsql *evsql, struct evsql_trans *trans, struct evsql_query *query, const char *command);
-
-/*
- * Free the query and related resources, doesn't trigger any callbacks or remove from any queues.
- *
- * The command should already be taken care of (NULL).
- */
-void _evsql_query_free (struct evsql_query *query);
-
-#endif /* EVSQL_INTERNAL_H */
--- a/src/evsql/query.c	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,256 +0,0 @@
-
-#include "evsql.h"
-#include "../lib/error.h"
-#include "../lib/misc.h"
-
-#include <stdlib.h>
-#include <assert.h>
-
-/*
- * Initialize params->types/values/lengths/formats, params->count, params->result_format based on the given args
- */
-static int _evsql_query_params_init_pq (struct evsql_query_params_pq *params, size_t param_count, enum evsql_item_format result_format) {
-    // set count
-    params->count = param_count;
-
-    // allocate vertical storage for the parameters
-    if (0
-        
-        ||  !(params->types     = calloc(param_count, sizeof(Oid)))
-        ||  !(params->values    = calloc(param_count, sizeof(char *)))
-        ||  !(params->lengths   = calloc(param_count, sizeof(int)))
-        ||  !(params->formats   = calloc(param_count, sizeof(int)))
-        ||  !(params->item_vals = calloc(param_count, sizeof(union evsql_item_value)))
-    )
-        ERROR("calloc");
-
-    // result format
-    switch (result_format) {
-        case EVSQL_FMT_TEXT:
-            params->result_format = 0; break;
-
-        case EVSQL_FMT_BINARY:
-            params->result_format = 1; break;
-
-        default:
-            FATAL("params.result_fmt: %d", result_format);
-    }
-    
-    // good
-    return 0;
-
-error:
-    return -1;
-}
-
-struct evsql_query *evsql_query (struct evsql *evsql, struct evsql_trans *trans, const char *command, evsql_query_cb query_fn, void *cb_arg) {
-    struct evsql_query *query = NULL;
-    
-    // alloc new query
-    if ((query = _evsql_query_new(evsql, trans, query_fn, cb_arg)) == NULL)
-        goto error;
-    
-    // just execute the command string directly
-    if (_evsql_query_enqueue(evsql, trans, query, command))
-        goto error;
-
-    // ok
-    return query;
-
-error:
-    _evsql_query_free(query);
-
-    return NULL;
-}
-
-struct evsql_query *evsql_query_params (struct evsql *evsql, struct evsql_trans *trans, 
-    const char *command, const struct evsql_query_params *params, 
-    evsql_query_cb query_fn, void *cb_arg
-) {
-    struct evsql_query *query = NULL;
-    const struct evsql_item *param;
-    size_t count = 0, idx;
-    
-    // alloc new query
-    if ((query = _evsql_query_new(evsql, trans, query_fn, cb_arg)) == NULL)
-        goto error;
-
-    // count the params
-    for (param = params->list; param->info.type; param++) 
-        count++;
-    
-    // initialize params
-    _evsql_query_params_init_pq(&query->params, count, params->result_format);
-
-    // transform
-    for (param = params->list, idx = 0; param->info.type; param++, idx++) {
-        // set for NULLs, otherwise not
-        query->params.types[idx] = (param->bytes || param->flags.has_value) ? 0 : EVSQL_PQ_ARBITRARY_TYPE_OID;
-
-        // scalar values
-        query->params.item_vals[idx] = param->value;
-        
-        // values
-        // point this at the value stored in the item_vals union if flagged as such
-        query->params.values[idx] = param->flags.has_value ? (const char *) &query->params.item_vals[idx] : param->bytes;
-
-        // lengths
-        query->params.lengths[idx] = param->length;
-        
-        // XXX: this assumes that format is FMT_BINARY...
-        query->params.formats[idx] = param->info.format;
-    }
-
-    // execute it
-    if (_evsql_query_enqueue(evsql, trans, query, command))
-        goto error;
-
-#ifdef DEBUG_ENABLED
-    // debug it?
-    DEBUG("evsql.%p: enqueued query=%p on trans=%p", evsql, query, trans);
-    evsql_query_debug(command, params);
-#endif /* DEBUG_ENABLED */
-
-    // ok
-    return query;
-
-error:
-    _evsql_query_free(query);
-    
-    return NULL;
-}
-
-struct evsql_query *evsql_query_exec (struct evsql *evsql, struct evsql_trans *trans, 
-    const struct evsql_query_info *query_info,
-    evsql_query_cb query_fn, void *cb_arg,
-    ...
-) {
-    va_list vargs;
-    struct evsql_query *query = NULL;
-    const struct evsql_item_info *param;
-    size_t count = 0, idx;
-    err_t err = 1;
-
-    // varargs
-    va_start(vargs, cb_arg);
-    
-    // alloc new query
-    if ((query = _evsql_query_new(evsql, trans, query_fn, cb_arg)) == NULL)
-        goto error;
-
-    // count the params
-    for (param = query_info->params; param->type; param++) 
-        count++;
-    
-    // initialize params
-    _evsql_query_params_init_pq(&query->params, count, EVSQL_FMT_BINARY);
-
-    // transform
-    for (param = query_info->params, idx = 0; param->type; param++, idx++) {
-        // default type to 0 (implicit)
-        query->params.types[idx] = 0;
-
-        // default format to binary
-        query->params.formats[idx] = EVSQL_FMT_BINARY;
-
-        // consume argument
-        switch (param->type) {
-            case EVSQL_TYPE_NULL_: {
-                // explicit type + text fmt
-                query->params.types[idx] = EVSQL_PQ_ARBITRARY_TYPE_OID;
-                query->params.values[idx] = NULL;
-                query->params.lengths[idx] = 0;
-                query->params.formats[idx] = EVSQL_FMT_TEXT;
-            } break;
-
-            case EVSQL_TYPE_BINARY: {
-                struct evsql_item_binary item = va_arg(vargs, struct evsql_item_binary);
-                
-                // value + explicit len
-                query->params.values[idx] = item.ptr;
-                query->params.lengths[idx] = item.len;
-            } break;
-
-            case EVSQL_TYPE_STRING: {
-                const char *str = va_arg(vargs, const char *);
-
-                // value + automatic length, text format
-                query->params.values[idx] = str;
-                query->params.lengths[idx] = 0;
-                query->params.formats[idx] = EVSQL_FMT_TEXT;
-            } break;
-            
-            case EVSQL_TYPE_UINT16: {
-                // XXX: uint16_t is passed as `int'?
-                uint16_t uval = va_arg(vargs, int);
-
-                if (uval != (int16_t) uval)
-                    ERROR("param $%zu: uint16 overflow: %d", idx + 1, uval);
-                
-                // network-byte-order value + explicit len
-                query->params.item_vals[idx].uint16 = htons(uval);
-                query->params.values[idx] = (const char *) &query->params.item_vals[idx];
-                query->params.lengths[idx] = sizeof(uint16_t);
-            } break;
-            
-            case EVSQL_TYPE_UINT32: {
-                uint32_t uval = va_arg(vargs, uint32_t);
-
-                if (uval != (int32_t) uval)
-                    ERROR("param $%zu: uint32 overflow: %ld", idx + 1, (unsigned long) uval);
-                
-                // network-byte-order value + explicit len
-                query->params.item_vals[idx].uint32 = htonl(uval);
-                query->params.values[idx] = (const char *) &query->params.item_vals[idx];
-                query->params.lengths[idx] = sizeof(uint32_t);
-            } break;
-
-            case EVSQL_TYPE_UINT64: {
-                uint64_t uval = va_arg(vargs, uint64_t);
-
-                if (uval != (int64_t) uval)
-                    ERROR("param $%zu: uint16 overflow: %lld", idx + 1, (unsigned long long) uval);
-                
-                // network-byte-order value + explicit len
-                query->params.item_vals[idx].uint64 = htonq(uval);
-                query->params.values[idx] = (const char *) &query->params.item_vals[idx];
-                query->params.lengths[idx] = sizeof(uint64_t);
-            } break;
-            
-            default: 
-                FATAL("param $%zu: invalid type: %d", idx + 1, param->type);
-        }
-    }
-
-    // execute it
-    if (_evsql_query_enqueue(evsql, trans, query, query_info->sql))
-        goto error;
-    
-    // no error, fallthrough for va_end
-    err = 0;
-
-error:
-    // possible cleanup
-    if (err)
-        _evsql_query_free(query);
-    
-    // end varargs
-    va_end(vargs);
-    
-    // return 
-    return err ? NULL : query;
-}
-
-
-void evsql_query_abort (struct evsql_trans *trans, struct evsql_query *query) {
-    assert(query);
-
-    if (trans) {
-        // must be the right query
-        assert(trans->query == query);
-    }
-
-    // just strip the callback and wait for it to complete as normal
-    query->cb_fn = NULL;
-}
-
--- a/src/evsql/result.c	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,258 +0,0 @@
-
-#include "evsql.h"
-#include "../lib/error.h"
-#include "../lib/misc.h"
-
-#include <stdlib.h>
-#include <assert.h>
-
-const char *evsql_result_error (const struct evsql_result *res) {
-    if (!res->error)
-        return "No error";
-
-    switch (res->evsql->type) {
-        case EVSQL_EVPQ:
-            if (!res->result.pq)
-                return "unknown error (no result)";
-            
-            return PQresultErrorMessage(res->result.pq);
-
-        default:
-            FATAL("res->evsql->type");
-    }
-
-}
-
-size_t evsql_result_rows (const struct evsql_result *res) {
-    switch (res->evsql->type) {
-        case EVSQL_EVPQ:
-            return PQntuples(res->result.pq);
-
-        default:
-            FATAL("res->evsql->type");
-    }
-}
-
-size_t evsql_result_cols (const struct evsql_result *res) {
-    switch (res->evsql->type) {
-        case EVSQL_EVPQ:
-            return PQnfields(res->result.pq);
-
-        default:
-            FATAL("res->evsql->type");
-    }
-}
-
-size_t evsql_result_affected (const struct evsql_result *res) {
-    switch (res->evsql->type) {
-        case EVSQL_EVPQ:
-            // XXX: errors?
-            return strtol(PQcmdTuples(res->result.pq), NULL, 10);
-
-        default:
-            FATAL("res->evsql->type");
-    }
-}
-
-
-int evsql_result_null (const struct evsql_result *res, size_t row, size_t col) {
-    switch (res->evsql->type) {
-        case EVSQL_EVPQ:
-            return PQgetisnull(res->result.pq, row, col);
-
-        default:
-            FATAL("res->evsql->type");
-    }
-}
-
-int evsql_result_field (const struct evsql_result *res, size_t row, size_t col, const char **ptr, size_t *size) {
-    *ptr = NULL;
-
-    switch (res->evsql->type) {
-        case EVSQL_EVPQ:
-            if (PQfformat(res->result.pq, col) != 1)
-                ERROR("[%zu:%zu] PQfformat is not binary: %d", row, col, PQfformat(res->result.pq, col));
-    
-            *size = PQgetlength(res->result.pq, row, col);
-            *ptr  = PQgetvalue(res->result.pq, row, col);
-
-            return 0;
-
-        default:
-            FATAL("res->evsql->type");
-    }
-
-error:
-    return -1;
-}
-
-err_t evsql_result_check (struct evsql_result *res) {
-    // so simple...
-    return res->error ? EIO : 0;
-}
-
-err_t evsql_result_begin (struct evsql_result_info *info, struct evsql_result *res) {
-    struct evsql_item_info *col;
-    size_t cols = 0, nrows;
-    err_t err;
-
-    // count info columns
-    for (col = info->columns; col->type; col++)
-        cols++;
-
-    // number of rows returned/affected
-    nrows = evsql_result_rows(res) || evsql_result_affected(res);
-
-    // did the query fail outright?
-    if (res->error)
-        // dump error message
-        NXERROR(err = EIO, evsql_result_error(res));
-
-/*
-    // SELECT/DELETE/UPDATE WHERE didn't match any rows -> ENOENT
-    if (nrows == 0)
-        XERROR(err = ENOENT, "no rows returned/affected");
-*/
-
-    // correct number of columns
-    if (evsql_result_cols(res) != cols)
-        XERROR(err = EINVAL, "wrong number of columns: %zu, should be %zu", evsql_result_cols(res), cols);
-    
-    // assign
-    res->info = info;
-    res->row_offset = 0;
-
-    // good
-    return 0;
-
-error:
-    return err;
-
-}
-
-int evsql_result_next (struct evsql_result *res, ...) {
-    va_list vargs;
-    struct evsql_item_info *col;
-    size_t col_idx, row_idx = res->row_offset;
-    err_t err;
-    
-    // ensure that evsql_result_begin has been called
-    assert(res->info);
-    
-    // check if we're past the end
-    if (row_idx >= evsql_result_rows(res))
-        return 0;
-    
-    // varargs
-    va_start(vargs, res);
-
-    for (col = res->info->columns, col_idx = 0; col->type; col++, col_idx++) {
-        const char *value = NULL;
-        size_t length = 0;
-        
-        // check for NULLs, then try and get the field value
-        if (evsql_result_null(res, row_idx, col_idx)) {
-            if (!col->flags.null_ok)
-                XERROR(err = EINVAL, "r%zu:c%zu: NULL", row_idx, col_idx);
-
-        } else if (evsql_result_field(res, row_idx, col_idx, &value, &length)) {
-            SERROR(err = EINVAL);
-
-        }
-        
-        // read the arg
-        switch (col->type) {
-            case EVSQL_TYPE_BINARY: {
-                struct evsql_item_binary *item_ptr = va_arg(vargs, struct evsql_item_binary *);
-
-                if (value) {
-                    item_ptr->ptr = value;
-                    item_ptr->len = length;
-                }
-            } break;
-
-            case EVSQL_TYPE_STRING: {
-                const char **str_ptr = va_arg(vargs, const char **);
-
-                if (value) {
-                    *str_ptr = value;
-                }
-
-            } break;
-
-            case EVSQL_TYPE_UINT16: {
-                uint16_t *uval_ptr = va_arg(vargs, uint16_t *);
-
-                if (!value) break;
-
-                if (length != sizeof(uint16_t)) XERROR(err = EINVAL, "r%zu:c%zu: wrong size for uint16_t: %zu", row_idx, col_idx, length);
-
-                int16_t sval = ntohs(*((int16_t *) value));
-
-                if (sval < 0) XERROR(err = ERANGE, "r%zu:c%zu: out of range for uint16_t: %hd", row_idx, col_idx, (signed short) sval);
-
-                *uval_ptr = sval;
-            } break;
-            
-            case EVSQL_TYPE_UINT32: {
-                uint32_t *uval_ptr = va_arg(vargs, uint32_t *);
-
-                if (!value) break;
-
-                if (length != sizeof(uint32_t)) XERROR(err = EINVAL, "r%zu:c%zu: wrong size for uint32_t: %zu", row_idx, col_idx, length);
-
-                int32_t sval = ntohl(*((int32_t *) value));
-
-                if (sval < 0) XERROR(err = ERANGE, "r%zu:c%zu: out of range for uint32_t: %ld", row_idx, col_idx, (signed long) sval);
-
-                *uval_ptr = sval;
-            } break;
-            
-            case EVSQL_TYPE_UINT64: {
-                uint64_t *uval_ptr = va_arg(vargs, uint64_t *);
-
-                if (!value) break;
-
-                if (length != sizeof(uint64_t)) XERROR(err = EINVAL, "r%zu:c%zu: wrong size for uint64_t: %zu", row_idx, col_idx, length);
-
-                int64_t sval = ntohq(*((int64_t *) value));
-
-                if (sval < 0) XERROR(err = ERANGE, "r%zu:c%zu: out of range for uint64_t: %lld", row_idx, col_idx, (signed long long) sval);
-
-                *uval_ptr = sval;
-            } break;
-            
-            default:
-                XERROR(err = EINVAL, "r%zu:c%zu: invalid type: %d", row_idx, col_idx, col->type);
-        }
-    }
-
-    // advance row index
-    res->row_offset++;
-
-    // row handled succesfully
-    return 1;
-
-error:
-    return -err;
-}
-
-void evsql_result_end (struct evsql_result *res) {
-    // not much more to it...
-    evsql_result_free(res);
-}
-
-void evsql_result_free (struct evsql_result *res) {
-    // note that the result itself might be NULL...
-    // in the case of internal-error results, these may be free'd multiple times!
-    switch (res->evsql->type) {
-        case EVSQL_EVPQ:
-            if (res->result.pq)
-                return PQclear(res->result.pq);
-
-        default:
-            FATAL("res->evsql->type");
-    }
-}
-
-
--- a/src/evsql/util.c	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,280 +0,0 @@
-#include <stdlib.h>
-#include <assert.h>
-
-#include "evsql.h"
-#include "../lib/log.h"
-#include "../lib/misc.h"
-
-#define _PARAM_TYPE_CASE(typenam) case EVSQL_TYPE_ ## typenam: return #typenam
-
-#define _PARAM_VAL_BUF_MAX 120
-#define _PARAM_VAL_CASE(typenam, ...) case EVSQL_TYPE_ ## typenam: if (item->bytes) ret = snprintf(buf, _PARAM_VAL_BUF_MAX, __VA_ARGS__); else return "(null)"; break
-
-const char *evsql_item_type (const struct evsql_item_info *item_info) {
-    switch (item_info->type) {
-        _PARAM_TYPE_CASE (INVALID   );
-        _PARAM_TYPE_CASE (NULL_     );
-        _PARAM_TYPE_CASE (BINARY    );
-        _PARAM_TYPE_CASE (STRING    );
-        _PARAM_TYPE_CASE (UINT16    );
-        _PARAM_TYPE_CASE (UINT32    );
-        _PARAM_TYPE_CASE (UINT64    );
-        default: return "???";
-    }
-}
-
-
-static const char *evsql_item_val (const struct evsql_item *item) {
-    static char buf[_PARAM_VAL_BUF_MAX];
-    int ret;
-
-    switch (item->info.type) {
-        _PARAM_VAL_CASE (INVALID,   "???"                               );
-        _PARAM_VAL_CASE (NULL_,     "(null)"                            );
-        _PARAM_VAL_CASE (BINARY,    "%zu:%s",   item->length, "... "    );
-        _PARAM_VAL_CASE (STRING,    "%s",       item->bytes             );
-        _PARAM_VAL_CASE (UINT16,    "%hu",      (unsigned short int)     ntohs(item->value.uint16)  );
-        _PARAM_VAL_CASE (UINT32,    "%lu",      (unsigned long int)      ntohl(item->value.uint32)  );
-        _PARAM_VAL_CASE (UINT64,    "%llu",     (unsigned long long int) ntohq(item->value.uint64)  );
-        default: return "???";
-    }
-
-    return buf;
-}
-
-int evsql_params_clear (struct evsql_query_params *params) {
-    struct evsql_item *param;
-
-    for (param = params->list; param->info.type; param++) {
-        param->bytes = NULL;
-        param->flags.has_value = 0;
-    }
-
-    return 0;
-}
-
-int evsql_param_null (struct evsql_query_params *params, size_t param) {
-    struct evsql_item *p = &params->list[param];
-
-    p->bytes = NULL;
-    p->flags.has_value = 0;
-
-    return 0;
-}
-
-int evsql_param_binary (struct evsql_query_params *params, size_t param, const char *ptr, size_t len) {
-    struct evsql_item *p = &params->list[param];
-    
-    assert(p->info.type == EVSQL_TYPE_BINARY);
-
-    p->bytes = ptr;
-    p->length = len;
-
-    return 0;
-}
-
-int evsql_param_string (struct evsql_query_params *params, size_t param, const char *ptr) {
-    struct evsql_item *p = &params->list[param];
-    
-    assert(p->info.type == EVSQL_TYPE_STRING);
-    
-    // XXX: hmm...
-    p->info.format = EVSQL_FMT_TEXT;
-
-    p->bytes = ptr;
-    p->length = 0;
-
-    return 0;
-}
-
-int evsql_param_uint16 (struct evsql_query_params *params, size_t param, uint16_t uval) {
-    struct evsql_item *p = &params->list[param];
-    
-    assert(p->info.type == EVSQL_TYPE_UINT16);
-
-    p->value.uint16 = htons(uval);
-    p->length = sizeof(uval);
-    p->flags.has_value = 1;
-
-    return 0;
-}
-
-int evsql_param_uint32 (struct evsql_query_params *params, size_t param, uint32_t uval) {
-    struct evsql_item *p = &params->list[param];
-    
-    assert(p->info.type == EVSQL_TYPE_UINT32);
-
-    p->value.uint32 = htonl(uval);
-    p->length = sizeof(uval);
-    p->flags.has_value = 1;
-
-    return 0;
-}
-
-void evsql_query_debug (const char *sql, const struct evsql_query_params *params) {
-    const struct evsql_item *param;
-    size_t param_count = 0, idx = 0;
-
-    // count the params
-    for (param = params->list; param->info.type; param++) 
-        param_count++;
-    
-    DEBUG("sql:     %s", sql);
-    DEBUG("params:  %zu", param_count);
-
-    for (param = params->list; param->info.type; param++) {
-        DEBUG("\t%2zu : %8s = %s", ++idx, evsql_item_type(&param->info), evsql_item_val(param));
-    }
-}
-
-int evsql_result_binary (const struct evsql_result *res, size_t row, size_t col, const char **ptr, size_t *size, bool nullok) {
-    *ptr = NULL;
-
-    switch (res->evsql->type) {
-        case EVSQL_EVPQ:
-            if (PQgetisnull(res->result.pq, row, col)) {
-                if (nullok)
-                    return 0;
-                else
-                    ERROR("[%zu:%zu] field is null", row, col);
-            }
-
-            if (PQfformat(res->result.pq, col) != 1)
-                ERROR("[%zu:%zu] PQfformat is not binary: %d", row, col, PQfformat(res->result.pq, col));
-    
-            *size = PQgetlength(res->result.pq, row, col);
-            *ptr  = PQgetvalue(res->result.pq, row, col);
-
-            return 0;
-
-        default:
-            FATAL("res->evsql->type");
-    }
-
-error:
-    return -1;
-}
-
-int evsql_result_binlen (const struct evsql_result *res, size_t row, size_t col, const char **ptr, size_t size, int nullok) {
-    size_t real_size = 0;
-
-    if (evsql_result_binary(res, row, col, ptr, &real_size, nullok))
-        goto error;
-    
-    if (*ptr == NULL) {
-        assert(nullok);
-        return 0;
-    }
-
-    if (size && real_size != size)
-        ERROR("[%zu:%zu] field size mismatch: %zu -> %zu", row, col, size, real_size);
-     
-    return 0;
-
-error:
-    return -1;
-}
-
-int evsql_result_string (const struct evsql_result *res, size_t row, size_t col, const char **ptr, int nullok) {
-    size_t real_size;
-
-    if (evsql_result_binary(res, row, col, ptr, &real_size, nullok))
-        goto error;
-
-    assert(real_size == strlen(*ptr));
-    
-    return 0;
-
-error:
-    return -1;
-}
-
-int evsql_result_uint16 (const struct evsql_result *res, size_t row, size_t col, uint16_t *uval, int nullok) {
-    const char *data;
-    int16_t sval;
-
-    if (evsql_result_binlen(res, row, col, &data, sizeof(*uval), nullok))
-        goto error;
-    
-    if (!data)
-        return 0;
-
-    sval = ntohs(*((int16_t *) data));
-
-    if (sval < 0)
-        ERROR("negative value for unsigned: %d", sval);
-
-    *uval = sval;
-    
-    return 0;
-
-error:
-    return nullok ? 0 : -1;
-}
-
-int evsql_result_uint32 (const struct evsql_result *res, size_t row, size_t col, uint32_t *uval, int nullok) {
-    const char *data;
-    int32_t sval;
-
-    if (evsql_result_binlen(res, row, col, &data, sizeof(*uval), nullok))
-        goto error;
-    
-    if (!data)
-        return 0;
-
-    sval = ntohl(*(int32_t *) data);
-
-    if (sval < 0)
-        ERROR("negative value for unsigned: %d", sval);
-
-    *uval = sval;
-    
-    return 0;
-
-error:
-    return nullok ? 0 : -1;
-}
-
-int evsql_result_uint64 (const struct evsql_result *res, size_t row, size_t col, uint64_t *uval, int nullok) {
-    const char *data;
-    int64_t sval;
-
-    if (evsql_result_binlen(res, row, col, &data, sizeof(*uval), nullok))
-        goto error;
-    
-    if (!data)
-        return 0;
-
-    sval = ntohq(*(int64_t *) data);
-
-    if (sval < 0)
-        ERROR("negative value for unsigned: %ld", sval);
-
-    *uval = sval;
-    
-    return 0;
-
-error:
-    return nullok ? 0 : -1;
-}
-
-const char *evsql_conn_error (struct evsql_conn *conn) {
-    switch (conn->evsql->type) {
-        case EVSQL_EVPQ:
-            if (!conn->engine.evpq)
-                return "unknown error (no conn)";
-            
-            return evpq_error_message(conn->engine.evpq);
-
-        default:
-            FATAL("res->evsql->type");
-    }
-}
-
-const char *evsql_trans_error (struct evsql_trans *trans) {
-    if (trans->conn == NULL)
-        return "unknown error (no trans conn)";
-
-    return evsql_conn_error(trans->conn);
-}
-
--- a/src/hello.c	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,247 +0,0 @@
-#include <string.h>
-#include <errno.h>
-#include <stdlib.h>
-
-#include <event2/event.h>
-#include <fuse/fuse_opt.h>
-
-#include "lib/log.h"
-#include "lib/math.h"
-#include "lib/signals.h"
-#include "evfuse.h"
-#include "dirbuf.h"
-
-const char *file_name = "hello";
-const char *file_data = "Hello World\n";
-
-static struct hello {
-    struct event_base *ev_base;
-
-    struct signals *signals;
-
-    struct evfuse *ev_fuse;
-
-} ctx;
-
-void hello_init (void *userdata, struct fuse_conn_info *conn) {
-    INFO("[hello.init] userdata=%p, conn=%p", userdata, conn);
-}
-
-void hello_destroy (void *userdata) {
-    INFO("[hello.destroy] userdata=%p", userdata);
-}
-
-void hello_lookup (fuse_req_t req, fuse_ino_t parent, const char *name) {
-    struct fuse_entry_param e;
-
-    INFO("[hello.lookup] (uid=%d, pid=%d) parent=%lu name=%s", fuse_req_ctx(req)->uid, fuse_req_ctx(req)->pid, parent, name);
-
-    // the world is flat
-    if (parent != 1 || strcmp(name, file_name)) {
-        fuse_reply_err(req, ENOENT);
-
-        return;
-    }
-    
-    // set up the entry
-    memset(&e, 0, sizeof(e));
-    e.ino = 2;
-    e.attr_timeout = 1.0;
-    e.entry_timeout = 1.0;
-    e.attr.st_mode = S_IFREG | 0444;
-    e.attr.st_nlink = 1;
-    e.attr.st_size = strlen(file_data);
-
-    // reply
-    fuse_reply_entry(req, &e);
-}
-
-void hello_getattr (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) {
-    struct stat stbuf;
-
-    INFO("[hello.getattr] (uid=%d, pid=%d) ino=%lu, fi=%p", fuse_req_ctx(req)->uid, fuse_req_ctx(req)->pid, ino, fi);
-
-    memset(&stbuf, 0, sizeof(stbuf));
-    
-    // the root dir, or the file?
-    if (ino == 1) {
-        stbuf.st_mode = S_IFDIR | 0555;
-        stbuf.st_nlink = 2;
-
-    } else if (ino == 2) {
-        stbuf.st_mode = S_IFREG | 0444;
-        stbuf.st_nlink = 1;
-        stbuf.st_size = strlen(file_data);
-
-    } else {
-        fuse_reply_err(req, ENOENT);
-        return;
-    }
-    
-    // reply
-    fuse_reply_attr(req, &stbuf, 1.0);
-}
-
-
-void hello_readdir (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) {
-    int err = 0;
-    struct dirbuf buf;
-
-    INFO("[hello.readdir] ino=%lu, size=%zu, off=%zu, fi=%p", ino, size, off, fi);
-
-    // there exists only one dir
-    if (ino != 1) {
-        fuse_reply_err(req, ENOTDIR);
-        return;
-    }
-
-    // fill in the dirbuf
-    if (dirbuf_init(&buf, size, off))
-        ERROR("failed to init dirbuf");
-
-    err =   dirbuf_add(req, &buf, 0, 1,  ".",        1,    S_IFDIR )
-        ||  dirbuf_add(req, &buf, 1, 2,  "..",       1,    S_IFDIR )
-        ||  dirbuf_add(req, &buf, 2, 3,  file_name,  2,    S_IFREG );
-
-    if (err < 0)
-        ERROR("failed to add dirents to buf");
-    
-    // send it
-    if ((err = -dirbuf_done(req, &buf)))
-        EERROR(-err, "failed to send buf");
-
-    // success
-    return;
-
-error:
-    if ((err = fuse_reply_err(req, err ? err : EIO)))
-        EWARNING(err, "failed to send error reply");
-}
-
-void hello_open (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) {
-    int err = 0;
-
-    INFO("[hello.open] ino=%lu, fi=%p, fi->flags=%08X", ino, fi, fi->flags);
-
-    if (ino != 2) {
-        // must open our only file, not the dir
-        fuse_reply_err(req, ino == 1 ? EISDIR : ENOENT);
-        return;
-
-    } else if ((fi->flags & 0x03) != O_RDONLY) {
-        // "permission denied"
-        fuse_reply_err(req, EACCES);
-        return;
-    }
-
-    // XXX: update fi stuff?
-
-    // open it!
-    if ((err = fuse_reply_open(req, fi)))
-        EERROR(err, "fuse_reply_open");
-
-    // success
-    return;
-
-error:
-    if ((err = fuse_reply_err(req, err ? err : EIO)))
-        EWARNING(err, "failed to send error reply");
-}
-
-void hello_read (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) {
-    int err = 0;
-
-    // fi is unused
-    (void) fi;
-
-    INFO("[hello.read] ino=%lu, size=%zu, off=%zu, fi=%p", ino, size, off, fi);
-
-    if (ino != 2) {
-        // EEK!
-        FATAL("wrong inode");
-    }
-    
-    if (off >= strlen(file_data)) {
-        // offset is out-of-file, so return EOF
-        err = fuse_reply_buf(req, NULL, 0);
-
-    } else {
-        // reply with the requested file data
-        err = fuse_reply_buf(req, file_data + off, MIN(strlen(file_data) - off, size));
-    }
-
-    // reply
-    if (err)
-        PERROR("fuse_reply_buf");
-    
-    // success
-    return;
-
-error:
-    if ((err = fuse_reply_err(req, err ? err : EIO)))
-        EWARNING(err, "failed to send error reply");
-}
-
-void hello_getxattr (fuse_req_t req, fuse_ino_t ino, const char *name, size_t size) {
-    INFO("[hello.getxattr] ino=%lu, name=`%s', size=%zu", ino, name, size);
-
-    fuse_reply_err(req, ENOSYS);
-}
-
-struct fuse_lowlevel_ops hello_llops = {
-    .init = &hello_init,
-    .destroy = &hello_destroy,
-
-    .lookup = &hello_lookup,
-    .getattr = &hello_getattr,
-
-    .open = &hello_open,
-
-    .read = &hello_read,
-
-    .readdir = &hello_readdir,
-
-    .getxattr = hello_getxattr,
-};
-
-
-int main (int argc, char **argv) {
-    struct fuse_args fuse_args = FUSE_ARGS_INIT(argc, argv);
-    
-    // zero
-    memset(&ctx, 0, sizeof(ctx));
-
-    // init libevent
-    if ((ctx.ev_base = event_base_new()) == NULL)
-        ERROR("event_base_new");
-    
-    // setup signals
-    if ((ctx.signals = signals_default(ctx.ev_base)) == NULL)
-        ERROR("signals_default");
-
-    // open fuse
-    if ((ctx.ev_fuse = evfuse_new(ctx.ev_base, &fuse_args, &hello_llops, &ctx)) == NULL)
-        ERROR("evfuse_new");
-
-    // run libevent
-    INFO("running libevent loop");
-
-    if (event_base_dispatch(ctx.ev_base))
-        PERROR("event_base_dispatch");
-    
-    // clean shutdown
-
-error :
-    // cleanup
-    if (ctx.ev_fuse)
-        evfuse_free(ctx.ev_fuse);
-    
-    if (ctx.signals)
-        signals_free(ctx.signals);
-
-    if (ctx.ev_base)
-        event_base_free(ctx.ev_base);
-    
-    fuse_opt_free_args(&fuse_args);
-}
-
--- a/src/helloworld.c	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-#include <stdio.h>
-
-int main (void) {
-    printf("Hello World\n");
-
-    return 0;
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/internal.h	Sun Mar 08 01:33:45 2009 +0200
@@ -0,0 +1,192 @@
+#ifndef EVSQL_INTERNAL_H
+#define EVSQL_INTERNAL_H
+
+/*
+ * Internal interfaces
+ */
+
+#include <sys/queue.h>
+
+#include <event2/event.h>
+
+#include "evsql.h"
+#include "evpq.h"
+
+/*
+ * The engine type
+ */
+enum evsql_type {
+    EVSQL_EVPQ,     // evpq
+};
+
+/*
+ * Contains the type, engine configuration, list of connections and waiting query queue.
+ */
+struct evsql {
+    // what event_base to use
+    struct event_base *ev_base;
+
+    // what engine we use
+    enum evsql_type type;
+
+    // callbacks
+    evsql_error_cb error_fn;
+    void *cb_arg;
+    
+    // engine-specific connection configuration
+    union {
+        const char *evpq;
+    } engine_conf;
+
+    // list of connections that are open
+    LIST_HEAD(evsql_conn_list, evsql_conn) conn_list;
+   
+    // list of queries running or waiting to run
+    TAILQ_HEAD(evsql_query_queue, evsql_query) query_queue;
+};
+
+/*
+ * A single connection to the server.
+ *
+ * Contains the engine connection, may have a transaction associated, and may have a query associated.
+ */
+struct evsql_conn {
+    // evsql we belong to
+    struct evsql *evsql;
+
+    // engine-specific connection info
+    union {
+        struct evpq_conn *evpq;
+    } engine;
+
+    // our position in the conn list
+    LIST_ENTRY(evsql_conn) entry;
+
+    // are we running a transaction?
+    struct evsql_trans *trans;
+
+    // are we running a transactionless query?
+    struct evsql_query *query;
+};
+
+/*
+ * A single transaction.
+ *
+ * Has a connection associated and possibly a query (which will also be associated with the connection)
+ */
+struct evsql_trans {
+    // our evsql_conn/evsql
+    struct evsql *evsql;
+    struct evsql_conn *conn;
+    
+    // callbacks
+    evsql_trans_error_cb error_fn;
+    evsql_trans_ready_cb ready_fn;
+    evsql_trans_done_cb done_fn;
+    void *cb_arg;
+
+    // the transaction type
+    enum evsql_trans_type type;
+
+    // has evsql_trans_commit be called?
+    int has_commit : 1;
+
+    // our current query
+    struct evsql_query *query;
+
+};
+
+/*
+ * Backend result handle
+ */
+union evsql_result_handle {
+    PGresult *pq;
+};
+
+/*
+ * A single query.
+ *
+ * Has the info needed to exec the query (as these may be queued), and the callback/result info.
+ */
+struct evsql_query {
+    // the actual SQL query, this may or may not be ours, see _evsql_query_exec
+    char *command;
+    
+    // possible query params
+    struct evsql_query_params_pq {
+        int count;
+
+        Oid *types;
+        const char **values;
+        int *lengths;
+        int *formats;
+
+        // storage for numeric values
+        union evsql_item_value *item_vals;
+
+        int result_format;
+    } params;
+
+    // our callback
+    evsql_query_cb cb_fn;
+    void *cb_arg;
+        
+    // the result we get
+    union evsql_result_handle result;
+
+    // our position in the query list
+    TAILQ_ENTRY(evsql_query) entry;
+};
+
+// the result
+struct evsql_result {
+    struct evsql *evsql;
+
+    // possible error code
+    int error;
+    
+    // the actual result
+    union evsql_result_handle result;
+
+    // result_* state
+    struct evsql_result_info *info;
+    size_t row_offset;
+};
+
+
+// maximum length for a 'BEGIN TRANSACTION ...' query
+#define EVSQL_QUERY_BEGIN_BUF 512
+
+// the should the OID of some valid psql type... *ANY* valid psql type, doesn't matter, only used for NULLs
+// 16 = bool in 8.3
+#define EVSQL_PQ_ARBITRARY_TYPE_OID 16
+
+/*
+ * Core query-submission interface.
+ *
+ * This performs some error-checking on the trans, allocates the evsql_query and does some basic initialization.
+ *
+ * This does not actually enqueue the query anywhere, no reference is stored anywhere.
+ *
+ * Returns the new evsql_query on success, NULL on failure.
+ */
+struct evsql_query *_evsql_query_new (struct evsql *evsql, struct evsql_trans *trans, evsql_query_cb query_fn, void *cb_arg);
+
+/*
+ * Begin processing the given query, which should now be fully filled out.
+ *
+ * If trans is given, it MUST be idle, and the query will be executed. Otherwise, it will either be executed directly
+ * or enqueued for future execution.
+ *
+ * Returns zero on success, nonzero on failure.
+ */
+int _evsql_query_enqueue (struct evsql *evsql, struct evsql_trans *trans, struct evsql_query *query, const char *command);
+
+/*
+ * Free the query and related resources, doesn't trigger any callbacks or remove from any queues.
+ *
+ * The command should already be taken care of (NULL).
+ */
+void _evsql_query_free (struct evsql_query *query);
+
+#endif /* EVSQL_INTERNAL_H */
--- a/src/lib/lex.c	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,181 +0,0 @@
-
-#include <stdlib.h>
-
-#include "lex.h"
-#include "error.h"
-#include "log.h"
-
-#define INITIAL_BUF_SIZE 4096
-
-int lexer (const struct lex *lex, const char *input, void *arg) {
-    // handling error returns
-    int err = -1, cb_err;
-    
-    // token buffer
-    char *buf = NULL, *buf_ptr;
-    size_t buf_size = INITIAL_BUF_SIZE;
-    
-    // state
-    int prev_state = LEX_INITIAL, cur_state = lex->initial_state, next_state = LEX_INITIAL;
-    
-    // input chars
-    const char *c = input;
-
-    // lookups
-    const struct lex_transition *trans = NULL;
-
-    // allocate the buffer
-    if ((buf = malloc(sizeof(char) * buf_size)) == NULL)
-        goto error;
-
-    // set buf_ptr initial position
-    buf_ptr = buf;
-    
-    // clear input
-    DEBUG("*cough*");
-    DEBUGN("%s", "");
-
-    // process input
-    do {
-        if (*c) {
-            // look up the next state
-            for (trans = lex->state_list[cur_state - 1].trans_list; trans->next_state > 0 || trans->flags; trans++) {
-                // accept defaults
-                if (trans->flags & LEX_TRANS_DEFAULT)
-                    break;
-                
-                // disregard non-matches
-                if (trans->left > *c || *c > trans->right)
-                    continue;
-                
-                // abort on invalids
-                if (trans->flags & LEX_TRANS_INVALID) {
-                    goto error;
-                
-                } else {
-                    // accept it
-                    break;
-                }
-            }
-            
-            // did we find a transition with a valid next state?
-            if (!(next_state = trans->next_state))
-                goto error;
-
-            // call the char handler
-            if (lex->char_fn && (cb_err = lex->char_fn(*c, cur_state, next_state, arg)))
-                goto error;
-
-        } else {
-            // EOF!
-            next_state = LEX_EOF;
-            
-            // is cur_state a valid end state?
-            if (!(lex->state_list[cur_state - 1].flags & LEX_STATE_END))
-                goto error;
-            
-            // note: we don't pass the NUL byte to the char handler
-        }
-
-        // if this char is part of the next token...
-        if (next_state != cur_state) {
-            // terminate the buffer and reset buf_ptr
-            *buf_ptr = 0; buf_ptr = buf;
-            
-            // dump state transitions
-            DEBUGF("\n\t%25s -> %25s -> %25s",
-                LEX_STATE_NAME(lex, prev_state),
-                LEX_STATE_NAME(lex, cur_state),
-                LEX_STATE_NAME(lex, next_state)
-            );
-
-            // pass in the complete token to the handler
-            if (lex->token_fn && (cb_err = lex->token_fn(cur_state, buf, next_state, prev_state, arg)))
-                goto error;
-
-            // update states
-            prev_state = cur_state;
-            cur_state = next_state;
-            next_state = LEX_INITIAL;
-        }
-        
-        // dump chars
-        if (next_state == LEX_INITIAL)
-            DEBUGN("%c", *c);
-        else
-            DEBUGNF("%c", *c);
-        
-        // store this char in the buffer
-        *(buf_ptr++) = *c;
-
-        // grow the buffer if needed
-        if (buf_ptr - buf >= buf_size) {
-            // remember the offset, as buf_ptr might get invalidated if buf is moved
-            size_t buf_offset = buf_ptr - buf;
-
-            // calc new size
-            buf_size *= 2;
-            
-            // grow/move
-            if ((buf = realloc(buf, buf_size)) == NULL)
-                goto error;
-            
-            // fix buf_ptr
-            buf_ptr = buf + buf_offset;
-        }
-    } while (*(c++));
-
-    // call the end handler
-    if (lex->end_fn && (cb_err = lex->end_fn(cur_state, arg)))
-        goto error;
-
-    // successfully parsed!
-    err = 0;
-
-error:
-    DEBUGNF("\n");
-    
-    if (cb_err)
-        err = cb_err;
-
-    // dump debug info on error
-    if (err) {
-        const char *cc;
-        
-        // figure out the error
-        if (!buf)
-            WARNING("malloc/realloc");
-
-        else if (trans && trans->flags & LEX_TRANS_INVALID)
-            WARNING("hit invalid transition match");
-
-        else if (!next_state)
-            WARNING("no valid transition found");
-            
-        else if (next_state == LEX_EOF && !(lex->state_list[cur_state - 1].flags & LEX_STATE_END))
-            WARNING("invalid end state");
-        
-        else
-            WARNING("unknown error condition (!?)");
-
-        DEBUG("%s", input);
-        DEBUGN("%s", "");
-
-        for (cc = input; cc < c; cc++)
-            DEBUGNF(" ");
-
-        DEBUGF("^\t%s -> %s -> %s",
-            LEX_STATE_NAME(lex, prev_state),
-            LEX_STATE_NAME(lex, cur_state),
-            LEX_STATE_NAME(lex, next_state)
-        );
-    }
-
-    // free stuff
-    free(buf);
-
-    // return
-    return err;
-}
-
-
--- a/src/lib/lex.h	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,143 +0,0 @@
-#ifndef LIB_LEXER_H
-#define LIB_LEXER_H
-
-/*
- * Simple FSM lexing
- *
- * The lexer is implemented as a Finite State Machine, consisting for a number of states, which then contain a set of
- * transitions, which move the lexer from state to state based on each char of input at a time.
- *
- * Whenever the state changes, the token callback is triggered with the collected token data.
- */
-
-#include <sys/types.h>
-
-/*
- * Transition flags
- */
-enum lex_transition_flags {
-    LEX_TRANS_DEFAULT   = 0x01,
-    /* not supported
-    LEX_TRANS_FINAL     = 0x02, */
-    LEX_TRANS_INVALID   = 0x04,
-};
-
-/*
- * A transition from one state to another.
- */
-struct lex_transition {
-    // applies to chars [left, right]
-    char left, right;
-    
-    // flags from lex_transition_flags
-    char flags;
-    
-    // next state to enter
-    int next_state;
-};
-
-/*
- * State flags
- */ 
-enum lex_state_flags {
-    LEX_STATE_END       = 0x01,
-};
-
-/*
- * A state
- */
-struct lex_state {
-    // the state name (for debugging)
-    const char *name;
-
-    // flags from lex_state_flags
-    char flags;
-
-    // list of transitions for this state, terminated by a transition with next_state=0
-    struct lex_transition trans_list[15];
-};
-
-/*
- * Special states, these are all defined as zero
- */
-
-// shows up in token_fn as the value of next_token when this_token is the last token.
-#define LEX_EOF 0
-
-// shows up as the initial value of prev_token
-#define LEX_INITIAL 0
-
-/*
- * Lex machine
- */
-struct lex {
-    /*
-     * Core token handler. Everytime a full token is lexed (i.e. the state changes), this will be called.
-     * `this_token` represents the full token that was parsed, and `token_data` is the token's value. `next_token`
-     * is the state that terminated this token, and `prev_token` was the token before this one.
-     *
-     * `token_data` is a buffer allocated by the lexer that the actual input data is copied into. Thence, it can be
-     * modified, as its contents will be replaced by the next token. Hence, if you need to keep hold of it, copy it.
-     *
-     * Return zero to have lexing continue, nonzero to stop lexing.
-     */
-    int (*token_fn) (int this_token, char *token_data, int next_token, int prev_token, void *arg);
-
-    /*
-     * Called on every char handled by the lexer.
-     *
-     * The NUL byte at the end of the input string is not passed to char_fn (why not?).
-     *
-     * Return zero to have lexing continue, nonzero to stop lexing.
-     */
-    int (*char_fn) (char token_char, int from_token, int to_token, void *arg);
-
-    /*
-     * Called when the end of input has been reached, `last_token` is the state that we terminated in.
-     *
-     * Return zero to indiciate that the input was valid, nonzero to indicate an error.
-     */
-    int (*end_fn) (int last_token, void *arg);
-    
-    // number of states
-    size_t state_count;
-
-    // initial state
-    int initial_state;
-
-    // array of lex_states, indexable by the state id.
-    struct lex_state state_list[];
-};
-
-/*
- * Helper macros for building the state_list
- */
-#define LEX_STATE(enum_val)     { #enum_val, 0,
-#define LEX_STATE_END(enum_val) { #enum_val, LEX_STATE_END,
-
-    #define LEX_CHAR(c, to)         { c, c, 0, to }
-    #define LEX_RANGE(l, r, to)     { l, r, 0, to }
-    #define LEX_ALPHA(to)           LEX_RANGE('a', 'z', to), LEX_RANGE('A', 'Z', to)
-    #define LEX_NUMBER(to)          LEX_RANGE('0', '9', to)
-    #define LEX_ALNUM(to)           LEX_ALPHA(to), LEX_NUMBER(to), LEX_CHAR('-', to), LEX_CHAR('_', to)
-    #define LEX_WHITESPACE(to)      LEX_CHAR(' ', to), LEX_CHAR('\n', to), LEX_CHAR('\t', to)
-    #define LEX_INVALID(c)          { c, c, LEX_TRANS_INVALID, 0 }
-
-    #define LEX_DEFAULT(to)         { 0, 0, LEX_TRANS_DEFAULT, to } \
-                                  }
-    #define LEX_END                 { 0, 0, 0, 0 } \
-                                  }
-
-/*
- * Helpers for handling states
- */ 
-#define LEX_STATE_NAME(lex, state) ((state) ? (lex)->state_list[(state) - 1].name : "...")
-
-/*
- * Lex it!
- *
- * Return zero to indiciate that the input was valid, nonzero otherwise.
- */
-int lexer (const struct lex *lex, const char *input, void *arg);
-
-#endif /* LIB_LEXER_H */
--- a/src/lib/signals.c	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,109 +0,0 @@
-#define _GNU_SOURCE
-#include <signal.h>
-#include <string.h>
-#include <stdlib.h>
-#include <assert.h>
-
-#include "signals.h"
-#include "log.h"
-
-struct signals {
-    struct event_base *ev_base;
-
-    struct signal {
-        struct event *ev;
-    } sig_list[MAX_SIGNALS];
-
-    int sig_count;
-};
-
-void signals_loopexit (int signal, short event, void *arg) {
-    struct signals *signals = arg;
-
-    INFO("[signal] caught %s: exiting the event loop", strsignal(signal));
-    
-    if (event_base_loopexit(signals->ev_base, NULL))
-        FATAL("event_base_loopexit");
-}
-
-void signals_ignore (int signal, short event, void *arg) {
-    struct signals *signals = arg;
-
-    (void) signals;
-    
-    /* ignore */
-}
-
-struct signals *signals_alloc (struct event_base *ev_base) {
-    struct signals *signals = NULL;
-
-    if ((signals = calloc(1, sizeof(*signals))) == NULL)
-        ERROR("calloc");
-    
-    // simple attributes
-    signals->ev_base = ev_base;
-
-    // done
-    return signals;
-
-error:
-    return NULL;
-}
-
-int signals_add (struct signals *signals, int sigval, void (*sig_handler)(evutil_socket_t, short, void *)) {
-    struct signal *sig_info;
-    
-    // find our sig_info
-    assert(signals->sig_count < MAX_SIGNALS);
-    sig_info = &signals->sig_list[signals->sig_count++];
-    
-    // set up the libevent signal events
-    if ((sig_info->ev = signal_new(signals->ev_base, sigval, sig_handler, signals)) == NULL)
-        PERROR("signal_new");
-
-    if (signal_add(sig_info->ev, NULL))
-        PERROR("signal_add");
-
-    // success
-    return 0;
-
-error:
-    return -1;
-}
-
-struct signals *signals_default (struct event_base *ev_base) {
-    struct signals *signals = NULL;
-    
-    // alloc signals
-    if ((signals = signals_alloc(ev_base)) == NULL)
-        return NULL;
-    
-    // add the set of default signals
-    if (    signals_add(signals,    SIGPIPE,    &signals_ignore)
-        ||  signals_add(signals,    SIGINT,     &signals_loopexit)
-    ) ERROR("signals_add");
-    
-    // success
-    return signals;
-
-error:
-    if (signals)
-        signals_free(signals);
-
-    return NULL;
-}   
-
-void signals_free (struct signals *signals) {
-    int i;
-    
-    // free all events
-    for (i = 0; i < signals->sig_count; i++) {
-        if (signal_del(signals->sig_list[i].ev))
-            PWARNING("signal_del");
-
-    }
-    
-    // free the info itself
-    free(signals);
-}
-
--- a/src/lib/signals.h	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,54 +0,0 @@
-#ifndef LIB_SIGNAL_H
-#define LIB_SIGNAL_H
-
-/*
- * Handle signals in a libevent-sane way
- */
-
-#include <event2/event.h>
-
-/*
- * How many signals we can define actions for
- */
-#define MAX_SIGNALS 8
-
-/*
- * info about a set of signals
- */
-struct signals;
-
-/*
- * Used as a handler for signals that should cause a loopexit.
- */
-void signals_loopexit (int signal, short event, void *arg);
-
-/*
- * Used to receive signals, but discard them.
- */
-void signals_ignore (int signal, short event, void *arg);
-
-/*
- * Allocate a signals struct, acting on the given ev_base.
- *
- * Returns NULL on failure
- */
-struct signals *signals_alloc (struct event_base *ev_base);
-
-/*
- * Add a signal to be handled by the given signals struct with the given handler.
- */
-int signals_add (struct signals *signals, int sigval, void (*sig_handler)(evutil_socket_t, short, void *));
-
-/*
- * Add a set of default signals
- *      SIGPIPE     signals_ignore
- *      SIGINT      signals_loopexit
- */
-struct signals *signals_default (struct event_base *ev_base);
-
-/*
- * Free the resources/handlers associated with the given signal handler
- */
-void signals_free (struct signals *signals);
-
-#endif /* LIB_SIGNAL_H */
--- a/src/lib/url.c	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,587 +0,0 @@
-#define _GNU_SOURCE
-#include <stdlib.h>
-#include <string.h>
-
-#include "url.h"
-#include "lex.h"
-#include "error.h"
-#include "log.h"
-#include "misc.h"
-
-enum url_token {
-    URL_INVALID,
-    
-    URL_BEGIN,
-
-    // kludge to resolve ambiguous URL_SCHEME/URL_USERNAME+URL_PASSWORD/URL_HOSTNAME+URL_SERVICE at the beginning
-    URL_BEGIN_ALNUM,
-    URL_BEGIN_COLON,
-
-    URL_SCHEME,
-    URL_SCHEME_SEP,
-    URL_SCHEME_END_COL,
-    URL_SCHEME_END_SLASH1,
-    URL_SCHEME_END_SLASH2,
-
-    // kludge to resolve ambiguous URL_USERNAME+URL_PASSWORD/URL_HOSTNAME+URL_SERVICE after a scheme 
-    URL_USERHOST_ALNUM,
-    URL_USERHOST_COLON,
-    URL_USERHOST_ALNUM2,
-    
-    URL_USERNAME,
-    URL_PASSWORD_SEP,
-    URL_PASSWORD,
-    URL_USERNAME_END,
-
-    URL_HOSTNAME,
-
-    URL_SERVICE_SEP,
-    URL_SERVICE,
-
-    URL_PATH_START,
-    URL_PATH,
-
-    URL_OPT_START,
-    URL_OPT_KEY,
-    URL_OPT_EQ,
-    URL_OPT_VAL,
-    URL_OPT_SEP,
-    
-    URL_MAX,
-};
-
-/*
- * Parser state
- */
-struct url_state {
-    // the URL to parse into
-    struct url *url;
-    
-    // our lookahead-kludge
-    const char *alnum, *alnum2;
-    
-};
-
-static int _url_append_scheme (struct url *url, const char *data, int copy) {
-    if (!url->schema) {
-        if ((url->schema = malloc(sizeof(struct url_schema) + (1 * sizeof(const char *)))) == NULL)
-            ERROR("malloc");
-
-        url->schema->count = 1;
-
-    } else {
-        url->schema->count++;
-        
-        // I'm starting to hate flexible array members...
-        if ((url->schema = realloc(url->schema, sizeof(struct url_schema) + url->schema->count * sizeof(const char *))) == NULL)
-            ERROR("realloc");
-    }
-    
-    if ((url->schema->list[url->schema->count - 1] = copy ? strdup(data) : data) == NULL)
-        ERROR("strdup");
-
-    // k
-    return 0;
-
-error:
-    return -1;
-}
-
-static struct url_opt *_url_get_opt (struct url *url, int new) {
-    if (!url->opts) {
-        if ((url->opts = malloc(sizeof(struct url_opts) + (1 * sizeof(struct url_opt)))) == NULL)
-            ERROR("malloc");
-
-        url->opts->count = 1;
-
-    } else if (new) {
-        url->opts->count++;
-
-        if ((url->opts = realloc(url->opts, sizeof(struct url_opts) + url->opts->count * sizeof(struct url_opt))) == NULL)
-            ERROR("realloc");
-    }
-    
-    // success
-    return &url->opts->list[url->opts->count - 1];
-
-error:
-    return NULL;
-}
-
-static int _url_append_opt_key (struct url *url, const char *key) {
-    struct url_opt *opt;
-
-    if ((opt = _url_get_opt(url, 1)) == NULL)
-        goto error;
-
-    if ((opt->key = strdup(key)) == NULL)
-        ERROR("strdup");
-
-    opt->value = NULL;
-
-    return 0;
-
-error:
-    return -1;
-} 
-
-static int _url_append_opt_val (struct url *url, const char *value) {
-    struct url_opt *opt;
-
-    if ((opt = _url_get_opt(url, 0)) == NULL)
-        goto error;
-
-    if ((opt->value = strdup(value)) == NULL)
-        ERROR("strdup");
-
-    return 0;
-
-error:
-    return -1;
-}
-
-static int url_lex_token (int _this_token, char *token_data, int _next_token, int _prev_token, void *arg);
-
-static struct lex url_lex = {
-    .token_fn = url_lex_token,
-    .char_fn = NULL,
-    .end_fn = NULL,
-
-    .state_count = URL_MAX,
-    .initial_state = URL_BEGIN,
-    .state_list = {
-        LEX_STATE ( URL_BEGIN ) {
-            LEX_ALNUM       (           URL_BEGIN_ALNUM         ),
-            LEX_CHAR        (   ':',    URL_SERVICE_SEP         ),
-            LEX_CHAR        (   '/',    URL_PATH_START          ),
-            LEX_CHAR        (   '?',    URL_OPT_START           ),
-            LEX_END
-        },
-        
-        // this can be URL_SCHEME, URL_USERNAME or URL_HOSTNAME
-        LEX_STATE_END ( URL_BEGIN_ALNUM ) {
-            LEX_CHAR        (   '+',    URL_SCHEME_SEP          ),  // it was URL_SCHEME
-            LEX_CHAR        (   ':',    URL_BEGIN_COLON         ), 
-            LEX_CHAR        (   '@',    URL_USERNAME_END        ),  // it was URL_USERNAME
-            LEX_CHAR        (   '/',    URL_PATH_START          ),  // it was URL_HOSTNAME
-            LEX_CHAR        (   '?',    URL_OPT_START           ),  // it was URL_HOSTNAME
-            LEX_DEFAULT     (           URL_BEGIN_ALNUM         )
-        },
-        
-        // this can be URL_SCHEME_END_COL, URL_USERNAME_END or URL_SERVICE_SEP
-        LEX_STATE ( URL_BEGIN_COLON ) {
-            LEX_CHAR        (   '/',    URL_SCHEME_END_SLASH1   ),  // it was URL_SCHEME
-            LEX_ALNUM       (           URL_USERHOST_ALNUM2     ),
-            LEX_END
-        },
-       
-
-        LEX_STATE ( URL_SCHEME ) { 
-            LEX_ALNUM       (           URL_SCHEME              ),
-            LEX_CHAR        (   '+',    URL_SCHEME_SEP          ),
-            LEX_CHAR        (   ':',    URL_SCHEME_END_COL      ),
-            LEX_END
-        },
-
-        LEX_STATE ( URL_SCHEME_SEP ) {
-            LEX_ALNUM       (           URL_SCHEME              ),
-            LEX_END
-        },
-
-        LEX_STATE ( URL_SCHEME_END_COL ) {
-            LEX_CHAR        (   '/',    URL_SCHEME_END_SLASH1   ),
-            LEX_END
-        },
-
-        LEX_STATE ( URL_SCHEME_END_SLASH1 ) {
-            LEX_CHAR        (   '/',    URL_SCHEME_END_SLASH2   ),
-            LEX_END
-        },
-
-        LEX_STATE_END ( URL_SCHEME_END_SLASH2 ) {
-            LEX_ALNUM       (           URL_USERHOST_ALNUM      ),
-            LEX_CHAR        (   ':',    URL_SERVICE_SEP         ),
-            LEX_CHAR        (   '/',    URL_PATH_START          ),
-            LEX_CHAR        (   '?',    URL_OPT_START           ),
-            LEX_END
-        },
-        
-        // this can be URL_USERNAME or URL_HOSTNAME
-        LEX_STATE_END ( URL_USERHOST_ALNUM ) {
-            LEX_CHAR        (   ':',    URL_USERHOST_COLON      ), 
-            LEX_CHAR        (   '@',    URL_USERNAME_END        ),  // it was URL_USERNAME
-            LEX_CHAR        (   '/',    URL_PATH_START          ),  // it was URL_HOSTNAME
-            LEX_CHAR        (   '?',    URL_OPT_START           ),  // it was URL_HOSTNAME
-            LEX_DEFAULT     (           URL_USERHOST_ALNUM      ),
-        },
-        
-        // this can be URL_USERNAME_END or URL_SERVICE_SEP
-        LEX_STATE ( URL_USERHOST_COLON ) {
-            LEX_ALNUM       (           URL_USERHOST_ALNUM2        ),
-            LEX_END
-        },
-        
-        // this can be URL_PASSWORD or URL_SERVICE
-        LEX_STATE_END ( URL_USERHOST_ALNUM2 ) {
-            LEX_CHAR        (   '@',    URL_USERNAME_END        ),  // it was URL_PASSSWORD
-            LEX_CHAR        (   '/',    URL_PATH_START          ),  // it was URL_SERVICE
-            LEX_CHAR        (   '?',    URL_OPT_START           ),  // it was URL_SERVICE
-            LEX_DEFAULT     (           URL_USERHOST_ALNUM2     ),
-        },
-        
-        // dummy states, covered by URL_USERHOST_ALNUM/URL_USERHOST_COLON/URL_USERHOST_ALNUM2
-        LEX_STATE ( URL_USERNAME ) {
-            LEX_END
-        },
-
-        LEX_STATE ( URL_PASSWORD_SEP ) {
-            LEX_END
-        },
-
-        LEX_STATE ( URL_PASSWORD ) {
-            LEX_END
-        },
-
-
-        LEX_STATE_END ( URL_USERNAME_END ) {
-            LEX_ALNUM       (           URL_HOSTNAME            ), 
-            LEX_CHAR        (   ':',    URL_SERVICE_SEP         ),
-            LEX_CHAR        (   '/',    URL_PATH_START          ),
-            LEX_CHAR        (   '?',    URL_OPT_START           ),
-            LEX_END
-        },
-
-
-        LEX_STATE_END ( URL_HOSTNAME ) {
-            LEX_ALNUM       (           URL_HOSTNAME            ), 
-            LEX_CHAR        (   ':',    URL_SERVICE_SEP         ),
-            LEX_CHAR        (   '/',    URL_PATH_START          ),
-            LEX_CHAR        (   '?',    URL_OPT_START           ),
-            LEX_END
-        },
-
-
-        LEX_STATE ( URL_SERVICE_SEP ) {
-            LEX_ALNUM       (           URL_SERVICE            ), 
-            LEX_CHAR        (   '/',    URL_PATH_START          ),
-            LEX_CHAR        (   '?',    URL_OPT_START           ),
-            LEX_END
-        },
-
-        LEX_STATE_END ( URL_SERVICE ) {
-            LEX_ALNUM       (           URL_SERVICE            ), 
-            LEX_CHAR        (   '/',    URL_PATH_START          ),
-            LEX_CHAR        (   '?',    URL_OPT_START           ),
-            LEX_END
-        },
-
-
-        LEX_STATE_END ( URL_PATH_START ) {
-            LEX_CHAR        (   '?',    URL_OPT_START           ),
-            LEX_DEFAULT     (           URL_PATH                ),
-        },
-
-        LEX_STATE_END ( URL_PATH ) {
-            LEX_CHAR        (   '?',    URL_OPT_START           ),
-            LEX_DEFAULT     (           URL_PATH                ),
-        },
-
-
-        LEX_STATE_END ( URL_OPT_START ) {
-            LEX_CHAR        (   '&',    URL_OPT_SEP             ),
-            LEX_INVALID     (   '='                             ),
-            LEX_DEFAULT     (           URL_OPT_KEY             ),
-        },
-
-        LEX_STATE_END ( URL_OPT_KEY ) {
-            LEX_CHAR        (   '&',    URL_OPT_SEP             ),
-            LEX_CHAR        (   '=',    URL_OPT_EQ              ),
-            LEX_DEFAULT     (           URL_OPT_KEY             ),
-        },
-
-        LEX_STATE_END ( URL_OPT_EQ ) {
-            LEX_CHAR        (   '&',    URL_OPT_SEP             ),
-            LEX_INVALID     (   '='                             ),
-            LEX_DEFAULT     (           URL_OPT_VAL             ),
-        },
-
-        LEX_STATE_END ( URL_OPT_VAL ) {
-            LEX_CHAR        (   '&',    URL_OPT_SEP             ),
-            LEX_INVALID     (   '='                             ),
-            LEX_DEFAULT     (           URL_OPT_VAL             ),
-        },
-
-        LEX_STATE_END ( URL_OPT_SEP ) {
-            LEX_CHAR        (   '&',    URL_OPT_SEP             ),
-            LEX_INVALID     (   '='                             ),
-            LEX_DEFAULT     (           URL_OPT_KEY             ),
-        },
-        
-        LEX_STATE ( URL_ERROR ) {
-            LEX_END
-        },
-    }
-};
-
-static int url_lex_token (int _this_token, char *token_data, int _next_token, int _prev_token, void *arg) {
-    enum url_token this_token = _this_token, next_token = _next_token, prev_token = _prev_token;
-    struct url_state *state = arg;
-    const char **copy_to = NULL;
-
-    (void) prev_token;
-    
-    switch (this_token) {
-        case URL_BEGIN:
-            // irrelevant
-            break;
-
-        case URL_BEGIN_ALNUM:
-            switch (next_token) {
-                case URL_SCHEME_SEP:
-                    // store the scheme
-                    if (_url_append_scheme(state->url, token_data, 1))
-                        goto error;
-                    
-                    break;
-                
-                case URL_USERNAME_END:
-                    // store the username
-                    copy_to = &state->url->username; break;
-                
-                case URL_PATH_START:
-                case URL_OPT_START:
-                case LEX_EOF:
-                    // store the hostname
-                    copy_to = &state->url->hostname; break;
-
-                case URL_BEGIN_COLON:
-                    // gah...
-                    copy_to = &state->alnum; break;
-                
-
-                default:
-                    FATAL("weird next token");
-            }
-            
-            break;
-
-        case URL_BEGIN_COLON:
-            switch (next_token) {
-                case URL_SCHEME_END_SLASH1:
-                    // store the schema
-                    if (_url_append_scheme(state->url, state->alnum, 0))
-                        goto error;
-                    
-                    state->alnum = NULL;
-
-                    break;
-                
-                case URL_USERHOST_ALNUM2:
-                    // gah..
-                    break;
-
-                default:
-                    FATAL("weird next token");
-            }
-
-            break;
-
-        case URL_SCHEME:
-            // store the scheme
-            if (_url_append_scheme(state->url, token_data, 1))
-                goto error;
-
-            break;
-    
-        case URL_SCHEME_SEP:
-            // ignore
-            break;
-
-        case URL_SCHEME_END_COL:
-        case URL_SCHEME_END_SLASH1:
-        case URL_SCHEME_END_SLASH2:
-            // ignore
-            break;
-        
-        case URL_USERHOST_ALNUM:
-            switch (next_token) {
-                case URL_USERNAME_END:
-                    // store the username
-                    copy_to = &state->url->username; break;
-                
-                case URL_PATH_START:
-                case URL_OPT_START:
-                case LEX_EOF:
-                    // store the hostname
-                    copy_to = &state->url->hostname; break;
-
-                case URL_USERHOST_COLON:
-                    // gah...
-                    copy_to = &state->alnum; break;
-
-                default:
-                    FATAL("weird next token");
-            }
-            
-            break;
-
-        case URL_USERHOST_COLON:
-            // ignore
-            break;
-
-        case URL_USERHOST_ALNUM2:
-            switch (next_token) {
-                case URL_USERNAME_END:
-                    // store the username and password
-                    state->url->username = state->alnum; state->alnum = NULL;
-                    copy_to = &state->url->password;
-
-                    break;
-
-                case URL_PATH_START:
-                case URL_OPT_START:
-                case LEX_EOF:
-                    // store the hostname and service
-                    state->url->hostname = state->alnum; state->alnum = NULL;
-                    copy_to = &state->url->service; break;
-
-                default:
-                    FATAL("weird next token");
-            }
-
-            break;
-
-        case URL_USERNAME:
-        case URL_PASSWORD_SEP:
-        case URL_PASSWORD:
-            FATAL("these should be overshadowed");
-        
-        case URL_USERNAME_END:
-            // ignore
-            break;
-
-        case URL_HOSTNAME:
-            // store
-            copy_to = &state->url->hostname; break;
-
-        case URL_SERVICE_SEP:
-            // ignore
-            break;
-
-        case URL_SERVICE:
-            // store
-            copy_to = &state->url->service; break;
-        
-        case URL_PATH_START:
-            // ignore
-            break;
-
-        case URL_PATH:
-            // store
-            copy_to = &state->url->path; break;
-
-        case URL_OPT_START:
-            // ignore
-            break;
-
-        case URL_OPT_KEY:
-            // store
-            if (_url_append_opt_key(state->url, token_data))
-                goto error;
-
-            break;
-
-        case URL_OPT_EQ:
-            // ignore
-            break;
-
-        case URL_OPT_VAL:
-            // store
-            if (_url_append_opt_val(state->url, token_data))
-                goto error;
-
-            break;
-        
-        case URL_OPT_SEP:
-            // ignore
-            break;
-        
-        default:
-            ERROR("invalid token");
-    }
-    
-    if (copy_to) {
-        // copy the token data
-        if ((*copy_to = strdup(token_data)) == NULL)
-            ERROR("strdup");
-    }
-
-    // good
-    return 0;
-
-error:
-    DEBUG("token: %s -> %s -> %s: %s", 
-        LEX_STATE_NAME(&url_lex, prev_token), LEX_STATE_NAME(&url_lex, this_token), LEX_STATE_NAME(&url_lex, next_token),
-        token_data
-    );
-    return -1;
-}
-
-
-int url_parse (struct url *url, const char *text) {
-    struct url_state state; ZINIT(state);
-    int ret;
-
-    // set up state
-    state.url = url;
-    
-    // parse it
-    if ((ret = lexer(&url_lex, text, &state)))
-        ERROR("invalid URL");
-
-    // success
-    return 0;
-
-error:
-    return -1;
-}
-
-static void _url_dump_part (const char *field, const char *val, FILE *stream) {
-    if (val) {
-        fprintf(stream, "%s=%s ", field, val);
-    }
-}
-
-void url_dump (const struct url *url, FILE *stream) {
-    int i;
-
-    if (url->schema) {
-        fprintf(stream, "schema=(");
-
-        for (i = 0; i < url->schema->count; i++) {
-            if (i > 0)
-                fprintf(stream, ",");
-
-            fprintf(stream, "%s", url->schema->list[i]);
-        }
-
-        fprintf(stream, ") ");
-    }
-
-    _url_dump_part("username", url->username, stream);
-    _url_dump_part("password", url->password, stream);
-    _url_dump_part("hostname", url->hostname, stream);
-    _url_dump_part("service", url->service, stream);
-    _url_dump_part("path", url->path, stream);
-
-    if (url->opts) {
-        fprintf(stream, "opts: ");
-
-        for (i = 0; i < url->opts->count; i++) {
-            fprintf(stream, "%s=%s ", url->opts->list[i].key, url->opts->list[i].value);
-        }
-    }
-
-    fprintf(stream, "\n");
-}
-
--- a/src/lib/url.h	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,63 +0,0 @@
-#ifndef LIB_URL_H
-#define LIB_URL_H
-
-/*
- * A trivial parser for simple URLs
- *
- * [ <scheme> [ "+" <scheme> [ ... ] ] "://" ] [ <username> [ ":" <password> ] "@" ] [ <hostname> ] [ ":" <service> ] [ "/" <path> ] [ "?" [ <key> [ "=" <value> ] ] [ "&" [ <key> [ "="     <value> ] ] [ ... ] ]
- *
- *  example.com
- *  tcp://example.com:7348/
- *  psql://postgres@localhost/test_db?charset=utf8
- *  
- */
-
-#include <sys/types.h>
-#include <stdio.h>
-
-/*
- * The schema
- */
-struct url_schema {
-    size_t count;
-    const char *list[];
-};
-
-/*
- * The options at the end
- */
-struct url_opts {
-    size_t count;
-    struct url_opt {
-        const char *key;
-        const char *value;
-    } list[];
-};
-
-/*
- * A parsed URL
- */
-struct url {
-    struct url_schema *schema;
-    const char *username;
-    const char *password;
-    const char *hostname;
-    const char *service;
-    const char *path;
-    struct url_opts *opts;
-};
-
-/*
- * Parse the given `text` as an URL, returning the result in `url`. Optional fields that are missing in the text will
- * cause those values to be returned unmodified.
- *
- * Returns zero if the url was valid and was parsed, nonzero if it was invalid.
- */
-int url_parse (struct url *url, const char *text);
-
-/*
- * Prints a url in a debug-output format.
- */
-void url_dump (const struct url *url, FILE *stream);
-
-#endif /* LIB_URL_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/query.c	Sun Mar 08 01:33:45 2009 +0200
@@ -0,0 +1,256 @@
+
+#include "internal.h"
+#include "lib/error.h"
+#include "lib/misc.h"
+
+#include <stdlib.h>
+#include <assert.h>
+
+/*
+ * Initialize params->types/values/lengths/formats, params->count, params->result_format based on the given args
+ */
+static int _evsql_query_params_init_pq (struct evsql_query_params_pq *params, size_t param_count, enum evsql_item_format result_format) {
+    // set count
+    params->count = param_count;
+
+    // allocate vertical storage for the parameters
+    if (0
+        
+        ||  !(params->types     = calloc(param_count, sizeof(Oid)))
+        ||  !(params->values    = calloc(param_count, sizeof(char *)))
+        ||  !(params->lengths   = calloc(param_count, sizeof(int)))
+        ||  !(params->formats   = calloc(param_count, sizeof(int)))
+        ||  !(params->item_vals = calloc(param_count, sizeof(union evsql_item_value)))
+    )
+        ERROR("calloc");
+
+    // result format
+    switch (result_format) {
+        case EVSQL_FMT_TEXT:
+            params->result_format = 0; break;
+
+        case EVSQL_FMT_BINARY:
+            params->result_format = 1; break;
+
+        default:
+            FATAL("params.result_fmt: %d", result_format);
+    }
+    
+    // good
+    return 0;
+
+error:
+    return -1;
+}
+
+struct evsql_query *evsql_query (struct evsql *evsql, struct evsql_trans *trans, const char *command, evsql_query_cb query_fn, void *cb_arg) {
+    struct evsql_query *query = NULL;
+    
+    // alloc new query
+    if ((query = _evsql_query_new(evsql, trans, query_fn, cb_arg)) == NULL)
+        goto error;
+    
+    // just execute the command string directly
+    if (_evsql_query_enqueue(evsql, trans, query, command))
+        goto error;
+
+    // ok
+    return query;
+
+error:
+    _evsql_query_free(query);
+
+    return NULL;
+}
+
+struct evsql_query *evsql_query_params (struct evsql *evsql, struct evsql_trans *trans, 
+    const char *command, const struct evsql_query_params *params, 
+    evsql_query_cb query_fn, void *cb_arg
+) {
+    struct evsql_query *query = NULL;
+    const struct evsql_item *param;
+    size_t count = 0, idx;
+    
+    // alloc new query
+    if ((query = _evsql_query_new(evsql, trans, query_fn, cb_arg)) == NULL)
+        goto error;
+
+    // count the params
+    for (param = params->list; param->info.type; param++) 
+        count++;
+    
+    // initialize params
+    _evsql_query_params_init_pq(&query->params, count, params->result_format);
+
+    // transform
+    for (param = params->list, idx = 0; param->info.type; param++, idx++) {
+        // set for NULLs, otherwise not
+        query->params.types[idx] = (param->bytes || param->flags.has_value) ? 0 : EVSQL_PQ_ARBITRARY_TYPE_OID;
+
+        // scalar values
+        query->params.item_vals[idx] = param->value;
+        
+        // values
+        // point this at the value stored in the item_vals union if flagged as such
+        query->params.values[idx] = param->flags.has_value ? (const char *) &query->params.item_vals[idx] : param->bytes;
+
+        // lengths
+        query->params.lengths[idx] = param->length;
+        
+        // XXX: this assumes that format is FMT_BINARY...
+        query->params.formats[idx] = param->info.format;
+    }
+
+    // execute it
+    if (_evsql_query_enqueue(evsql, trans, query, command))
+        goto error;
+
+#ifdef DEBUG_ENABLED
+    // debug it?
+    DEBUG("evsql.%p: enqueued query=%p on trans=%p", evsql, query, trans);
+    evsql_query_debug(command, params);
+#endif /* DEBUG_ENABLED */
+
+    // ok
+    return query;
+
+error:
+    _evsql_query_free(query);
+    
+    return NULL;
+}
+
+struct evsql_query *evsql_query_exec (struct evsql *evsql, struct evsql_trans *trans, 
+    const struct evsql_query_info *query_info,
+    evsql_query_cb query_fn, void *cb_arg,
+    ...
+) {
+    va_list vargs;
+    struct evsql_query *query = NULL;
+    const struct evsql_item_info *param;
+    size_t count = 0, idx;
+    err_t err = 1;
+
+    // varargs
+    va_start(vargs, cb_arg);
+    
+    // alloc new query
+    if ((query = _evsql_query_new(evsql, trans, query_fn, cb_arg)) == NULL)
+        goto error;
+
+    // count the params
+    for (param = query_info->params; param->type; param++) 
+        count++;
+    
+    // initialize params
+    _evsql_query_params_init_pq(&query->params, count, EVSQL_FMT_BINARY);
+
+    // transform
+    for (param = query_info->params, idx = 0; param->type; param++, idx++) {
+        // default type to 0 (implicit)
+        query->params.types[idx] = 0;
+
+        // default format to binary
+        query->params.formats[idx] = EVSQL_FMT_BINARY;
+
+        // consume argument
+        switch (param->type) {
+            case EVSQL_TYPE_NULL_: {
+                // explicit type + text fmt
+                query->params.types[idx] = EVSQL_PQ_ARBITRARY_TYPE_OID;
+                query->params.values[idx] = NULL;
+                query->params.lengths[idx] = 0;
+                query->params.formats[idx] = EVSQL_FMT_TEXT;
+            } break;
+
+            case EVSQL_TYPE_BINARY: {
+                struct evsql_item_binary item = va_arg(vargs, struct evsql_item_binary);
+                
+                // value + explicit len
+                query->params.values[idx] = item.ptr;
+                query->params.lengths[idx] = item.len;
+            } break;
+
+            case EVSQL_TYPE_STRING: {
+                const char *str = va_arg(vargs, const char *);
+
+                // value + automatic length, text format
+                query->params.values[idx] = str;
+                query->params.lengths[idx] = 0;
+                query->params.formats[idx] = EVSQL_FMT_TEXT;
+            } break;
+            
+            case EVSQL_TYPE_UINT16: {
+                // XXX: uint16_t is passed as `int'?
+                uint16_t uval = va_arg(vargs, int);
+
+                if (uval != (int16_t) uval)
+                    ERROR("param $%zu: uint16 overflow: %d", idx + 1, uval);
+                
+                // network-byte-order value + explicit len
+                query->params.item_vals[idx].uint16 = htons(uval);
+                query->params.values[idx] = (const char *) &query->params.item_vals[idx];
+                query->params.lengths[idx] = sizeof(uint16_t);
+            } break;
+            
+            case EVSQL_TYPE_UINT32: {
+                uint32_t uval = va_arg(vargs, uint32_t);
+
+                if (uval != (int32_t) uval)
+                    ERROR("param $%zu: uint32 overflow: %ld", idx + 1, (unsigned long) uval);
+                
+                // network-byte-order value + explicit len
+                query->params.item_vals[idx].uint32 = htonl(uval);
+                query->params.values[idx] = (const char *) &query->params.item_vals[idx];
+                query->params.lengths[idx] = sizeof(uint32_t);
+            } break;
+
+            case EVSQL_TYPE_UINT64: {
+                uint64_t uval = va_arg(vargs, uint64_t);
+
+                if (uval != (int64_t) uval)
+                    ERROR("param $%zu: uint16 overflow: %lld", idx + 1, (unsigned long long) uval);
+                
+                // network-byte-order value + explicit len
+                query->params.item_vals[idx].uint64 = htonq(uval);
+                query->params.values[idx] = (const char *) &query->params.item_vals[idx];
+                query->params.lengths[idx] = sizeof(uint64_t);
+            } break;
+            
+            default: 
+                FATAL("param $%zu: invalid type: %d", idx + 1, param->type);
+        }
+    }
+
+    // execute it
+    if (_evsql_query_enqueue(evsql, trans, query, query_info->sql))
+        goto error;
+    
+    // no error, fallthrough for va_end
+    err = 0;
+
+error:
+    // possible cleanup
+    if (err)
+        _evsql_query_free(query);
+    
+    // end varargs
+    va_end(vargs);
+    
+    // return 
+    return err ? NULL : query;
+}
+
+
+void evsql_query_abort (struct evsql_trans *trans, struct evsql_query *query) {
+    assert(query);
+
+    if (trans) {
+        // must be the right query
+        assert(trans->query == query);
+    }
+
+    // just strip the callback and wait for it to complete as normal
+    query->cb_fn = NULL;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/result.c	Sun Mar 08 01:33:45 2009 +0200
@@ -0,0 +1,258 @@
+
+#include "internal.h"
+#include "lib/error.h"
+#include "lib/misc.h"
+
+#include <stdlib.h>
+#include <assert.h>
+
+const char *evsql_result_error (const struct evsql_result *res) {
+    if (!res->error)
+        return "No error";
+
+    switch (res->evsql->type) {
+        case EVSQL_EVPQ:
+            if (!res->result.pq)
+                return "unknown error (no result)";
+            
+            return PQresultErrorMessage(res->result.pq);
+
+        default:
+            FATAL("res->evsql->type");
+    }
+
+}
+
+size_t evsql_result_rows (const struct evsql_result *res) {
+    switch (res->evsql->type) {
+        case EVSQL_EVPQ:
+            return PQntuples(res->result.pq);
+
+        default:
+            FATAL("res->evsql->type");
+    }
+}
+
+size_t evsql_result_cols (const struct evsql_result *res) {
+    switch (res->evsql->type) {
+        case EVSQL_EVPQ:
+            return PQnfields(res->result.pq);
+
+        default:
+            FATAL("res->evsql->type");
+    }
+}
+
+size_t evsql_result_affected (const struct evsql_result *res) {
+    switch (res->evsql->type) {
+        case EVSQL_EVPQ:
+            // XXX: errors?
+            return strtol(PQcmdTuples(res->result.pq), NULL, 10);
+
+        default:
+            FATAL("res->evsql->type");
+    }
+}
+
+
+int evsql_result_null (const struct evsql_result *res, size_t row, size_t col) {
+    switch (res->evsql->type) {
+        case EVSQL_EVPQ:
+            return PQgetisnull(res->result.pq, row, col);
+
+        default:
+            FATAL("res->evsql->type");
+    }
+}
+
+int evsql_result_field (const struct evsql_result *res, size_t row, size_t col, const char **ptr, size_t *size) {
+    *ptr = NULL;
+
+    switch (res->evsql->type) {
+        case EVSQL_EVPQ:
+            if (PQfformat(res->result.pq, col) != 1)
+                ERROR("[%zu:%zu] PQfformat is not binary: %d", row, col, PQfformat(res->result.pq, col));
+    
+            *size = PQgetlength(res->result.pq, row, col);
+            *ptr  = PQgetvalue(res->result.pq, row, col);
+
+            return 0;
+
+        default:
+            FATAL("res->evsql->type");
+    }
+
+error:
+    return -1;
+}
+
+err_t evsql_result_check (struct evsql_result *res) {
+    // so simple...
+    return res->error ? EIO : 0;
+}
+
+err_t evsql_result_begin (struct evsql_result_info *info, struct evsql_result *res) {
+    struct evsql_item_info *col;
+    size_t cols = 0, nrows;
+    err_t err;
+
+    // count info columns
+    for (col = info->columns; col->type; col++)
+        cols++;
+
+    // number of rows returned/affected
+    nrows = evsql_result_rows(res) || evsql_result_affected(res);
+
+    // did the query fail outright?
+    if (res->error)
+        // dump error message
+        NXERROR(err = EIO, evsql_result_error(res));
+
+/*
+    // SELECT/DELETE/UPDATE WHERE didn't match any rows -> ENOENT
+    if (nrows == 0)
+        XERROR(err = ENOENT, "no rows returned/affected");
+*/
+
+    // correct number of columns
+    if (evsql_result_cols(res) != cols)
+        XERROR(err = EINVAL, "wrong number of columns: %zu, should be %zu", evsql_result_cols(res), cols);
+    
+    // assign
+    res->info = info;
+    res->row_offset = 0;
+
+    // good
+    return 0;
+
+error:
+    return err;
+
+}
+
+int evsql_result_next (struct evsql_result *res, ...) {
+    va_list vargs;
+    struct evsql_item_info *col;
+    size_t col_idx, row_idx = res->row_offset;
+    err_t err;
+    
+    // ensure that evsql_result_begin has been called
+    assert(res->info);
+    
+    // check if we're past the end
+    if (row_idx >= evsql_result_rows(res))
+        return 0;
+    
+    // varargs
+    va_start(vargs, res);
+
+    for (col = res->info->columns, col_idx = 0; col->type; col++, col_idx++) {
+        const char *value = NULL;
+        size_t length = 0;
+        
+        // check for NULLs, then try and get the field value
+        if (evsql_result_null(res, row_idx, col_idx)) {
+            if (!col->flags.null_ok)
+                XERROR(err = EINVAL, "r%zu:c%zu: NULL", row_idx, col_idx);
+
+        } else if (evsql_result_field(res, row_idx, col_idx, &value, &length)) {
+            SERROR(err = EINVAL);
+
+        }
+        
+        // read the arg
+        switch (col->type) {
+            case EVSQL_TYPE_BINARY: {
+                struct evsql_item_binary *item_ptr = va_arg(vargs, struct evsql_item_binary *);
+
+                if (value) {
+                    item_ptr->ptr = value;
+                    item_ptr->len = length;
+                }
+            } break;
+
+            case EVSQL_TYPE_STRING: {
+                const char **str_ptr = va_arg(vargs, const char **);
+
+                if (value) {
+                    *str_ptr = value;
+                }
+
+            } break;
+
+            case EVSQL_TYPE_UINT16: {
+                uint16_t *uval_ptr = va_arg(vargs, uint16_t *);
+
+                if (!value) break;
+
+                if (length != sizeof(uint16_t)) XERROR(err = EINVAL, "r%zu:c%zu: wrong size for uint16_t: %zu", row_idx, col_idx, length);
+
+                int16_t sval = ntohs(*((int16_t *) value));
+
+                if (sval < 0) XERROR(err = ERANGE, "r%zu:c%zu: out of range for uint16_t: %hd", row_idx, col_idx, (signed short) sval);
+
+                *uval_ptr = sval;
+            } break;
+            
+            case EVSQL_TYPE_UINT32: {
+                uint32_t *uval_ptr = va_arg(vargs, uint32_t *);
+
+                if (!value) break;
+
+                if (length != sizeof(uint32_t)) XERROR(err = EINVAL, "r%zu:c%zu: wrong size for uint32_t: %zu", row_idx, col_idx, length);
+
+                int32_t sval = ntohl(*((int32_t *) value));
+
+                if (sval < 0) XERROR(err = ERANGE, "r%zu:c%zu: out of range for uint32_t: %ld", row_idx, col_idx, (signed long) sval);
+
+                *uval_ptr = sval;
+            } break;
+            
+            case EVSQL_TYPE_UINT64: {
+                uint64_t *uval_ptr = va_arg(vargs, uint64_t *);
+
+                if (!value) break;
+
+                if (length != sizeof(uint64_t)) XERROR(err = EINVAL, "r%zu:c%zu: wrong size for uint64_t: %zu", row_idx, col_idx, length);
+
+                int64_t sval = ntohq(*((int64_t *) value));
+
+                if (sval < 0) XERROR(err = ERANGE, "r%zu:c%zu: out of range for uint64_t: %lld", row_idx, col_idx, (signed long long) sval);
+
+                *uval_ptr = sval;
+            } break;
+            
+            default:
+                XERROR(err = EINVAL, "r%zu:c%zu: invalid type: %d", row_idx, col_idx, col->type);
+        }
+    }
+
+    // advance row index
+    res->row_offset++;
+
+    // row handled succesfully
+    return 1;
+
+error:
+    return -err;
+}
+
+void evsql_result_end (struct evsql_result *res) {
+    // not much more to it...
+    evsql_result_free(res);
+}
+
+void evsql_result_free (struct evsql_result *res) {
+    // note that the result itself might be NULL...
+    // in the case of internal-error results, these may be free'd multiple times!
+    switch (res->evsql->type) {
+        case EVSQL_EVPQ:
+            if (res->result.pq)
+                return PQclear(res->result.pq);
+
+        default:
+            FATAL("res->evsql->type");
+    }
+}
+
+
--- a/src/simple.c	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,283 +0,0 @@
-#include <string.h>
-#include <errno.h>
-#include <stdlib.h>
-#include <assert.h>
-
-#include "simple.h"
-#include "dirbuf.h"
-#include "lib/log.h"
-#include "lib/math.h"
-#include "lib/misc.h"
-
-struct simple_fs {
-    const struct simple_node *inode_table;
-
-    size_t inode_count;
-};
-
-/*
- * Used for stat/entry timeouts... not sure how this should really be set.
- */
-#define CACHE_TIMEOUT 1.0
-
-static void _simple_stat (struct stat *stat, const struct simple_node *node) {
-    stat->st_ino = node->inode;
-    stat->st_mode = node->mode_type | node->mode_perm;
-    stat->st_nlink = 1;
-    stat->st_size = node->data ? strlen(node->data) : 0;
-}
-
-/*
- * Fetch the simple_node for the given inode.
- *
- * Returns NULL for invalid inodes.
- */
-static const struct simple_node *_simple_get_ino (struct simple_fs *fs, fuse_ino_t ino) {
-    // make sure it's a valid inode
-    if (ino < 1 || ino > fs->inode_count) {
-        WARNING("invalid inode=%zu", ino);
-        return NULL;
-    }
-    
-    // return the node
-    return fs->inode_table + (ino - 1);
-}
-
-static void simple_lookup (fuse_req_t req, fuse_ino_t parent, const char *name) {
-    struct simple_fs *fs = fuse_req_userdata(req);
-    const struct simple_node *node;
-    struct fuse_entry_param e; ZINIT(e);
-    int err;
-    
-    INFO("[simple.lookup %p] parent=%lu, name=`%s'", fs, parent, name);
-
-    // find the matching node
-    for (node = fs->inode_table; node->inode > 0; node++) {
-        if (node->parent == parent && strcmp(node->name, name) == 0)
-            break;
-
-    }
-
-    // did we find it?
-    if (node->inode) {
-        // set up the entry
-        e.ino = node->inode;
-        e.generation = 0x01;
-        _simple_stat(&e.attr, node);
-        e.attr_timeout = CACHE_TIMEOUT;
-        e.entry_timeout = CACHE_TIMEOUT;
-
-        // reply
-        if ((err = fuse_reply_entry(req, &e)))
-            EERROR(err, "fuse_reply_entry");
-
-    } else {
-        // not found
-        err = ENOENT;
-        goto error;
-    }
-
-    // success
-    return;
-
-error:
-    if ((err = fuse_reply_err(req, err)))
-        EWARNING(err, "fuse_reply_err");
-}
-
-static void simple_getattr (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) {
-    struct simple_fs *fs = fuse_req_userdata(req);
-    const struct simple_node *node;
-    struct stat stbuf; ZINIT(stbuf);
-    int err;
-
-    INFO("[simple.getattr %p] ino=%lu", fs, ino);
-    
-    // look up the node 
-    if ((node = _simple_get_ino(fs, ino)) == NULL)
-        EERROR(err = EINVAL, "bad inode");
-    
-    // set up the stbuf
-    _simple_stat(&stbuf, node);
-    
-    // reply
-    if ((err = fuse_reply_attr(req, &stbuf, CACHE_TIMEOUT)))
-        EERROR(err, "fuse_reply_attr");
-    
-    // suceccss
-    return;
-
-error:
-    if ((err = fuse_reply_err(req, err)))
-        EWARNING(err, "fuse_reply_err");
-}
-
-static void simple_readlink (fuse_req_t req, fuse_ino_t ino) {
-    struct simple_fs *fs = fuse_req_userdata(req);
-    const struct simple_node *node;
-    int err;
-
-    INFO("[simple.readlink %p] ino=%lu", fs, ino);
-    
-    // look up the node 
-    if ((node = _simple_get_ino(fs, ino)) == NULL)
-        EERROR(err = EINVAL, "bad inode");
-
-    // check that it's a symlink
-    if (node->mode_type != S_IFLNK)
-        EERROR(err = EINVAL, "bad mode");
-
-    // return the contents
-    if ((err = fuse_reply_readlink(req, node->data)))
-        EERROR(err, "fuse_reply_readlink");
-
-    // suceccss
-    return;
-
-error:
-    if ((err = fuse_reply_err(req, err)))
-        EWARNING(err, "fuse_reply_err");
-
-}
-
-static void simple_readdir (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) {
-    struct simple_fs *fs = fuse_req_userdata(req);
-    const struct simple_node *dir_node, *node;
-    struct dirbuf buf;
-    int err;
-
-    INFO("[simple.readdir] ino=%lu, size=%zu, off=%zu, fi=%p", ino, size, off, fi);
-    
-    // look up the inode
-    if ((dir_node = _simple_get_ino(fs, ino)) == NULL)
-        EERROR(err = EINVAL, "bad inode");
-    
-    // check that it's a dir
-    if (dir_node->mode_type != S_IFDIR)
-        EERROR(err = ENOTDIR, "bad mode");
-
-    // fill in the dirbuf
-    if (dirbuf_init(&buf, size, off))
-        ERROR("failed to init dirbuf");
-    
-    // add . and ..
-    // we set the next offset to 2, because all dirent offsets will be larger than that
-    err =   dirbuf_add(req, &buf, 0, 1, ".",   dir_node->inode,    S_IFDIR )
-        ||  dirbuf_add(req, &buf, 1, 2, "..",  dir_node->inode,    S_IFDIR );
-    
-    if (err != 0)
-        EERROR(err, "failed to add . and .. dirents");
-
-    // look up all child nodes
-    for (node = fs->inode_table; node->inode; node++) {
-        // skip non-children
-        if (node->parent != dir_node->inode)
-            continue;
-        
-        // child node offsets are just inode + 2
-        if ((err = dirbuf_add(req, &buf, node->inode + 2, node->inode + 3, node->name, node->inode, node->mode_type)) < 0)
-            EERROR(err, "failed to add dirent for inode=%lu", node->inode);
-        
-        // stop if it's full
-        if (err > 0)
-            break;
-    }
-
-    // send it
-    if ((err = -dirbuf_done(req, &buf)))
-        EERROR(err, "failed to send buf");
-
-    // success
-    return;
-
-error:
-    if ((err = fuse_reply_err(req, err)))
-        EWARNING(err, "fuse_reply_err");
-}
-
-static void simple_read (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) {
-    struct simple_fs *fs = fuse_req_userdata(req);
-    const struct simple_node *node;
-    int err ;
-
-    // fi is unused
-    (void) fi;
-
-    INFO("[simple.read] ino=%lu, size=%zu, off=%zu, fi=%p", ino, size, off, fi);
-    
-    // look up the inode
-    if ((node = _simple_get_ino(fs, ino)) == NULL)
-        EERROR(err = EINVAL, "bad inode");
-    
-    // check that it's a dir
-    if (node->mode_type != S_IFREG)
-        EERROR(err = (node->mode_type == S_IFDIR ? EISDIR : EINVAL), "bad mode");
-    
-    // seek past EOF?
-    if (off >= strlen(node->data)) {
-        // offset is out-of-file, so return EOF
-        if ((err = fuse_reply_buf(req, NULL, 0)))
-            EERROR(err, "fuse_reply_buf size=0");
-
-    } else {
-        // reply with the requested file data
-        if ((err = fuse_reply_buf(req, node->data + off, MIN(strlen(node->data) - off, size))))
-            EERROR(err, "fuse_reply_buf buf=%p + %zu, size=MIN(%zu, %zu)", node->data, off, strlen(node->data) - off, size);
-    }
-
-    // success
-    return;
-
-error:
-    if ((err = fuse_reply_err(req, err)))
-        EWARNING(err, "fuse_reply_err");
-}
-
-
-/*
- * Define our fuse_lowlevel_ops struct.
- */
-static struct fuse_lowlevel_ops simple_ops = {
-    .lookup = simple_lookup,
-
-    .getattr = simple_getattr,
-
-    .readlink = simple_readlink,
-
-    .readdir = simple_readdir,
-
-    .read = simple_read,
-};
-
-struct fuse_lowlevel_ops *simple_init () {
-    return &simple_ops;
-}
-
-struct simple_fs *simple_new (const struct simple_node *node_list) {
-    struct simple_fs *fs = NULL;
-    const struct simple_node *node;
-    
-    // generate
-    if ((fs = calloc(1, sizeof(*fs))) == NULL)
-        ERROR("calloc");
-
-    // remember node_list
-    fs->inode_count = 0;
-    fs->inode_table = node_list;
-    
-    // validate it
-    for (node = fs->inode_table; node->inode; node++) {
-        // update inode_count
-        fs->inode_count++;
-
-        // check that parent is valid
-        assert(node->inode == fs->inode_count);
-        assert(node->parent < node->inode);
-    }
-    
-    // success
-    return fs;
-
-error:
-    return NULL;
-}
--- a/src/simple.h	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-#ifndef SIMPLE_H
-#define SIMPLE_H
-
-/*
- * A simple static in-memory filesystem structure.
- */
-
-#include "evfuse.h"
-
-/*
- * A simple file/dir.
- */
-struct simple_node {
-    // inode number
-    fuse_ino_t inode;
-
-    // mode
-    mode_t mode_type;
-    mode_t mode_perm;
-
-    // parent node
-    fuse_ino_t parent;
-
-    // name
-    const char *name;
-    
-    // data
-    const char *data;
-};
-
-/*
- * General information.
- */
-struct simple_fs;
-
-/*
- * Initialize simple, and get the fuse_lowlevel_ops.
- */
-struct fuse_lowlevel_ops *simple_init ();
-
-/*
- * Create a new simple_fs.
- */
-struct simple_fs *simple_new (const struct simple_node *node_list);
-
-#endif /* SIMPLE_H */
--- a/src/simple_hello.c	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,77 +0,0 @@
-
-#include <event2/event.h>
-
-#include "lib/log.h"
-#include "lib/signals.h"
-#include "evfuse.h"
-#include "simple.h"
-
-static struct hello {
-    struct event_base *ev_base;
-
-    struct signals *signals;
-
-    struct simple_fs *fs;
-
-    struct evfuse *ev_fuse;
-
-} ctx;
-
-static struct simple_node node_list[] = {
-    {   1,  S_IFDIR,    0555,   0,  NULL,       NULL                },
-    {   2,  S_IFREG,    0444,   1,  "hello",    "Hello World!\n"    },
-    {   3,  S_IFREG,    0444,   1,  "foo",      "Foo\n"             },
-    {   4,  S_IFREG,    0444,   1,  "bar",      "Bar\n"             },
-    {   5,  S_IFDIR,    0555,   1,  "test",     NULL                },
-    {   6,  S_IFREG,    0444,   5,  "file0",    "data0\n"           },
-    {   7,  S_IFREG,    0444,   5,  "file1",    "data1\n"           },
-    {   8,  S_IFLNK,    0444,   1,  "lnk0",     "test/file0"        },
-    {   0,  0,          0,      0,  NULL,       NULL                },
-};
-
-int main (int argc, char **argv) {
-    struct fuse_args fuse_args = FUSE_ARGS_INIT(argc, argv);
-    
-    // init libevent
-    if ((ctx.ev_base = event_base_new()) == NULL)
-        ERROR("event_base_new");
-    
-    // setup signals
-    if ((ctx.signals = signals_default(ctx.ev_base)) == NULL)
-        ERROR("signals_default");
-    
-    // setup fs
-    if ((ctx.fs = simple_new(node_list)) == NULL)
-        ERROR("simple_new");
-
-    // open fuse
-    if ((ctx.ev_fuse = evfuse_new(ctx.ev_base, &fuse_args, simple_init(), ctx.fs)) == NULL)
-        ERROR("evfuse_new");
-
-    // run libevent
-    INFO("running libevent loop");
-
-    if (event_base_dispatch(ctx.ev_base))
-        PERROR("event_base_dispatch");
-    
-    // clean shutdown
-
-error :
-    // cleanup
-    if (ctx.ev_fuse)
-        evfuse_free(ctx.ev_fuse);
-
-/*
-    if (ctx.fs)
-        simple_close(ctx.fs);
-*/
-
-    if (ctx.signals)
-        signals_free(ctx.signals);
-
-    if (ctx.ev_base)
-        event_base_free(ctx.ev_base);
-    
-    fuse_opt_free_args(&fuse_args);
-}
-
--- a/src/url_test.c	Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,198 +0,0 @@
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-
-#include "lib/url.h"
-
-#define FAIL(...) do { printf("FAIL: "); printf(__VA_ARGS__); printf("\n"); return -1; } while (0)
-
-struct url_schema
-    basic_http = { 1, { "http" } },
-    svn_ssh = { 2, { "svn", "ssh" } },
-    schema_unix = { 1, { "unix" } }
-    ;
-
-struct url_opts
-    opts_single = { 1, { { "key0", "val0" } } },
-    opts_multi = { 2, { { "key0", "val0" }, { "key1", "val1" } } },
-    opts_nullval = { 1, { { "keyN", NULL } } }
-    ;
-
-struct url_test {
-    const char *url;
-    const struct url expected;
-} url_tests[] = {
-    {   "localhost:http",   {
-        NULL, NULL, NULL, "localhost", "http", NULL, NULL
-    } },
-
-    {   "http://example.com/path",  {
-        &basic_http, NULL, NULL, "example.com", NULL, "path", NULL 
-    } },
-
-    {   "svn+ssh://user:passwd@someplace:someport/something",   {
-        &svn_ssh, "user", "passwd", "someplace", "someport", "something"
-    } },
-
-    {   "user@:service/",   {
-        NULL, "user", NULL, NULL, "service", NULL
-    } },
-
-    {   "unix:////tmp/foo.sock",    {
-        &schema_unix, NULL, NULL, NULL, NULL, "/tmp/foo.sock"
-    } },
-
-    {   "unix:///tmp/foo.sock", {
-        &schema_unix, NULL, NULL, NULL, NULL, "tmp/foo.sock"
-    } },
-
-    {   "/tmp/foo.sock",    {
-        NULL, NULL, NULL, NULL, NULL, "tmp/foo.sock"
-    } },
-
-    {   "?key0=val0",   {
-        NULL, NULL, NULL, NULL, NULL, NULL, &opts_single
-    } },
-
-    {   "http://foo.com/index.php?key0=val0&key1=val1",  {
-        &basic_http, NULL, NULL, "foo.com", NULL, "index.php", &opts_multi
-    } },
-
-    {   "example.org:81/?keyN", {
-        NULL, NULL, NULL, "example.org", "81", NULL, &opts_nullval
-    } },
-
-    {   NULL,               {   } },
-};
-
-int cmp_url_str (const char *field, const char *test, const char *real) {
-    if (!test) {
-        if (real)
-            FAIL("%s shouldn't be present", field);
-
-    } else if (!real) {
-        FAIL("%s is missing", field);
-
-    } else {
-        if (strcmp(test, real) != 0)
-            FAIL("%s differs: %s -> %s", field, test, real);
-    }
-
-    // ok
-    return 0;
-}
-
-int cmp_url (const struct url *test, const struct url *real) {
-    int i;
-
-    // test schema
-    if (!test->schema) {
-        if (real->schema)
-            FAIL("test has no schema, but real does");
-
-    } else if (!real->schema) {
-        FAIL("test has a schema, but real doesn't");
-
-    } else {
-        if (test->schema->count != test->schema->count)
-            FAIL("inconsistent scheme count");
-        
-        for (i = 0; i < test->schema->count; i++) {
-            if (strcmp(test->schema->list[i], real->schema->list[i]) != 0)
-                FAIL("differing scheme #%d", i);
-        }
-    }
-    
-    // test username
-    if (cmp_url_str("username", test->username, real->username))
-        goto error;
-
-    // test password
-    if (cmp_url_str("password", test->password, real->password))
-        goto error;
-
-    // test hostname
-    if (cmp_url_str("hostname", test->hostname, real->hostname))
-        goto error;
-
-    // test service
-    if (cmp_url_str("service", test->service, real->service))
-        goto error;
-
-    // test path
-    if (cmp_url_str("path", test->path, real->path))
-        goto error;
-
-    // test query
-    if (!test->opts) {
-        if (real->opts)
-            FAIL("test has no opts, but real does");
-
-    } else if (!real->opts) {
-        FAIL("test has opts, but real doesn't");
-
-    } else {
-        if (test->opts->count != test->opts->count)
-            FAIL("inconsistent opts count");
-        
-        for (i = 0; i < test->opts->count; i++) {
-            if (cmp_url_str("opt key", test->opts->list[i].key, real->opts->list[i].key))
-                FAIL("differing opt key #%d", i);
-            
-            if (cmp_url_str("opt value", test->opts->list[i].value, real->opts->list[i].value))
-                FAIL("differing opt value #%d", i);
-        }
-    }
-
-    // ok
-    return 0;
-
-error:
-    return -1;
-}
-
-void usage (const char *exec_name) {
-    printf("Usage: %s\n\n\tNo arguments are accepted\n", exec_name);
-
-    exit(EXIT_FAILURE);
-}
-
-int main (int argc, char **argv) {
-    const struct url_test *test;
-    struct url url;
-
-    if (argc > 1)
-        usage(argv[0]);
-
-    // run the tests
-    for (test = url_tests; test->url; test++) {
-        // first output the URL we are handling...
-        printf("%-80s - ", test->url);
-        fflush(stdout);
-        
-        // parse the URL
-        memset(&url, 0, sizeof(url));
-
-        if (url_parse(&url, test->url)) {
-            printf("FATAL: url_parse failed\n");
-            return EXIT_FAILURE;
-        }
-        
-        // compare it
-        if (cmp_url(&test->expected, &url)) {
-            printf("\texpected: ");
-            url_dump(&test->expected, stdout);
-
-            printf("\tresult:   ");
-            url_dump(&url, stdout);
-
-        } else {
-            printf("OK\n\t");
-            url_dump(&url, stdout);
-        }
-
-        printf("\n");
-    }
-}
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/util.c	Sun Mar 08 01:33:45 2009 +0200
@@ -0,0 +1,280 @@
+#include <stdlib.h>
+#include <assert.h>
+
+#include "internal.h"
+#include "lib/log.h"
+#include "lib/misc.h"
+
+#define _PARAM_TYPE_CASE(typenam) case EVSQL_TYPE_ ## typenam: return #typenam
+
+#define _PARAM_VAL_BUF_MAX 120
+#define _PARAM_VAL_CASE(typenam, ...) case EVSQL_TYPE_ ## typenam: if (item->bytes) ret = snprintf(buf, _PARAM_VAL_BUF_MAX, __VA_ARGS__); else return "(null)"; break
+
+const char *evsql_item_type (const struct evsql_item_info *item_info) {
+    switch (item_info->type) {
+        _PARAM_TYPE_CASE (INVALID   );
+        _PARAM_TYPE_CASE (NULL_     );
+        _PARAM_TYPE_CASE (BINARY    );
+        _PARAM_TYPE_CASE (STRING    );
+        _PARAM_TYPE_CASE (UINT16    );
+        _PARAM_TYPE_CASE (UINT32    );
+        _PARAM_TYPE_CASE (UINT64    );
+        default: return "???";
+    }
+}
+
+
+static const char *evsql_item_val (const struct evsql_item *item) {
+    static char buf[_PARAM_VAL_BUF_MAX];
+    int ret;
+
+    switch (item->info.type) {
+        _PARAM_VAL_CASE (INVALID,   "???"                               );
+        _PARAM_VAL_CASE (NULL_,     "(null)"                            );
+        _PARAM_VAL_CASE (BINARY,    "%zu:%s",   item->length, "... "    );
+        _PARAM_VAL_CASE (STRING,    "%s",       item->bytes             );
+        _PARAM_VAL_CASE (UINT16,    "%hu",      (unsigned short int)     ntohs(item->value.uint16)  );
+        _PARAM_VAL_CASE (UINT32,    "%lu",      (unsigned long int)      ntohl(item->value.uint32)  );
+        _PARAM_VAL_CASE (UINT64,    "%llu",     (unsigned long long int) ntohq(item->value.uint64)  );
+        default: return "???";
+    }
+
+    return buf;
+}
+
+int evsql_params_clear (struct evsql_query_params *params) {
+    struct evsql_item *param;
+
+    for (param = params->list; param->info.type; param++) {
+        param->bytes = NULL;
+        param->flags.has_value = 0;
+    }
+
+    return 0;
+}
+
+int evsql_param_null (struct evsql_query_params *params, size_t param) {
+    struct evsql_item *p = &params->list[param];
+
+    p->bytes = NULL;
+    p->flags.has_value = 0;
+
+    return 0;
+}
+
+int evsql_param_binary (struct evsql_query_params *params, size_t param, const char *ptr, size_t len) {
+    struct evsql_item *p = &params->list[param];
+    
+    assert(p->info.type == EVSQL_TYPE_BINARY);
+
+    p->bytes = ptr;
+    p->length = len;
+
+    return 0;
+}
+
+int evsql_param_string (struct evsql_query_params *params, size_t param, const char *ptr) {
+    struct evsql_item *p = &params->list[param];
+    
+    assert(p->info.type == EVSQL_TYPE_STRING);
+    
+    // XXX: hmm...
+    p->info.format = EVSQL_FMT_TEXT;
+
+    p->bytes = ptr;
+    p->length = 0;
+
+    return 0;
+}
+
+int evsql_param_uint16 (struct evsql_query_params *params, size_t param, uint16_t uval) {
+    struct evsql_item *p = &params->list[param];
+    
+    assert(p->info.type == EVSQL_TYPE_UINT16);
+
+    p->value.uint16 = htons(uval);
+    p->length = sizeof(uval);
+    p->flags.has_value = 1;
+
+    return 0;
+}
+
+int evsql_param_uint32 (struct evsql_query_params *params, size_t param, uint32_t uval) {
+    struct evsql_item *p = &params->list[param];
+    
+    assert(p->info.type == EVSQL_TYPE_UINT32);
+
+    p->value.uint32 = htonl(uval);
+    p->length = sizeof(uval);
+    p->flags.has_value = 1;
+
+    return 0;
+}
+
+void evsql_query_debug (const char *sql, const struct evsql_query_params *params) {
+    const struct evsql_item *param;
+    size_t param_count = 0, idx = 0;
+
+    // count the params
+    for (param = params->list; param->info.type; param++) 
+        param_count++;
+    
+    DEBUG("sql:     %s", sql);
+    DEBUG("params:  %zu", param_count);
+
+    for (param = params->list; param->info.type; param++) {
+        DEBUG("\t%2zu : %8s = %s", ++idx, evsql_item_type(&param->info), evsql_item_val(param));
+    }
+}
+
+int evsql_result_binary (const struct evsql_result *res, size_t row, size_t col, const char **ptr, size_t *size, bool nullok) {
+    *ptr = NULL;
+
+    switch (res->evsql->type) {
+        case EVSQL_EVPQ:
+            if (PQgetisnull(res->result.pq, row, col)) {
+                if (nullok)
+                    return 0;
+                else
+                    ERROR("[%zu:%zu] field is null", row, col);
+            }
+
+            if (PQfformat(res->result.pq, col) != 1)
+                ERROR("[%zu:%zu] PQfformat is not binary: %d", row, col, PQfformat(res->result.pq, col));
+    
+            *size = PQgetlength(res->result.pq, row, col);
+            *ptr  = PQgetvalue(res->result.pq, row, col);
+
+            return 0;
+
+        default:
+            FATAL("res->evsql->type");
+    }
+
+error:
+    return -1;
+}
+
+int evsql_result_binlen (const struct evsql_result *res, size_t row, size_t col, const char **ptr, size_t size, int nullok) {
+    size_t real_size = 0;
+
+    if (evsql_result_binary(res, row, col, ptr, &real_size, nullok))
+        goto error;
+    
+    if (*ptr == NULL) {
+        assert(nullok);
+        return 0;
+    }
+
+    if (size && real_size != size)
+        ERROR("[%zu:%zu] field size mismatch: %zu -> %zu", row, col, size, real_size);
+     
+    return 0;
+
+error:
+    return -1;
+}
+
+int evsql_result_string (const struct evsql_result *res, size_t row, size_t col, const char **ptr, int nullok) {
+    size_t real_size;
+
+    if (evsql_result_binary(res, row, col, ptr, &real_size, nullok))
+        goto error;
+
+    assert(real_size == strlen(*ptr));
+    
+    return 0;
+
+error:
+    return -1;
+}
+
+int evsql_result_uint16 (const struct evsql_result *res, size_t row, size_t col, uint16_t *uval, int nullok) {
+    const char *data;
+    int16_t sval;
+
+    if (evsql_result_binlen(res, row, col, &data, sizeof(*uval), nullok))
+        goto error;
+    
+    if (!data)
+        return 0;
+
+    sval = ntohs(*((int16_t *) data));
+
+    if (sval < 0)
+        ERROR("negative value for unsigned: %d", sval);
+
+    *uval = sval;
+    
+    return 0;
+
+error:
+    return nullok ? 0 : -1;
+}
+
+int evsql_result_uint32 (const struct evsql_result *res, size_t row, size_t col, uint32_t *uval, int nullok) {
+    const char *data;
+    int32_t sval;
+
+    if (evsql_result_binlen(res, row, col, &data, sizeof(*uval), nullok))
+        goto error;
+    
+    if (!data)
+        return 0;
+
+    sval = ntohl(*(int32_t *) data);
+
+    if (sval < 0)
+        ERROR("negative value for unsigned: %d", sval);
+
+    *uval = sval;
+    
+    return 0;
+
+error:
+    return nullok ? 0 : -1;
+}
+
+int evsql_result_uint64 (const struct evsql_result *res, size_t row, size_t col, uint64_t *uval, int nullok) {
+    const char *data;
+    int64_t sval;
+
+    if (evsql_result_binlen(res, row, col, &data, sizeof(*uval), nullok))
+        goto error;
+    
+    if (!data)
+        return 0;
+
+    sval = ntohq(*(int64_t *) data);
+
+    if (sval < 0)
+        ERROR("negative value for unsigned: %ld", sval);
+
+    *uval = sval;
+    
+    return 0;
+
+error:
+    return nullok ? 0 : -1;
+}
+
+const char *evsql_conn_error (struct evsql_conn *conn) {
+    switch (conn->evsql->type) {
+        case EVSQL_EVPQ:
+            if (!conn->engine.evpq)
+                return "unknown error (no conn)";
+            
+            return evpq_error_message(conn->engine.evpq);
+
+        default:
+            FATAL("res->evsql->type");
+    }
+}
+
+const char *evsql_trans_error (struct evsql_trans *trans) {
+    if (trans->conn == NULL)
+        return "unknown error (no trans conn)";
+
+    return evsql_conn_error(trans->conn);
+}
+