strip dbfs code away, and wrangle the code a bit
authorTero Marttila <terom@fixme.fi>
Sun, 08 Mar 2009 00:19:12 +0200
changeset 56 9dfc861273e5
parent 42 40a3b13ffc9d
child 57 527d23bf6441
strip dbfs code away, and wrangle the code a bit
include/evsql.h
src/core.c
src/dbfs.c
src/dbfs.h
src/dbfs/attr.c
src/dbfs/common.c
src/dbfs/dbfs.c
src/dbfs/dbfs.h
src/dbfs/dirop.c
src/dbfs/fileop.c
src/dbfs/interrupt.c
src/dbfs/link.c
src/dbfs/mk.c
src/dbfs/op_base.c
src/dbfs/op_base.h
src/dbfs/ops.h
src/dbfs/trans.c
src/dbfs/trans.h
src/dbfs/tree.c
src/dirbuf.c
src/dirbuf.h
src/evfuse.c
src/evfuse.h
src/evsql.h
src/evsql/evsql.c
src/evsql/evsql.h
src/evsql/util.c
src/hello.c
src/helloworld.c
src/internal.h
src/lib/lex.c
src/lib/lex.h
src/lib/signals.c
src/lib/signals.h
src/lib/url.c
src/lib/url.h
src/simple.c
src/simple.h
src/simple_hello.c
src/url_test.c
src/util.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/include/evsql.h	Sun Mar 08 00:19:12 2009 +0200
@@ -0,0 +1,276 @@
+#ifndef EVSQL_H
+#define EVSQL_H
+
+/*
+ * An event-based (Postgre)SQL client API using libevent
+ */
+
+// XXX: libpq
+#include <stdint.h>
+#include <postgresql/libpq-fe.h>
+#include <event2/event.h>
+
+/*
+ * The generic context handle
+ */
+struct evsql;
+
+/*
+ * A query handle
+ */
+struct evsql_query;
+
+/*
+ * Transaction handle
+ */
+struct evsql_trans;
+
+/*
+ * Transaction type
+ */
+enum evsql_trans_type {
+    EVSQL_TRANS_DEFAULT,
+    EVSQL_TRANS_SERIALIZABLE,
+    EVSQL_TRANS_REPEATABLE_READ,
+    EVSQL_TRANS_READ_COMMITTED,
+    EVSQL_TRANS_READ_UNCOMMITTED,
+};
+
+/*
+ * Parameter type
+ */
+enum evsql_param_format {
+    EVSQL_FMT_TEXT,
+    EVSQL_FMT_BINARY,
+};
+
+enum evsql_param_type {
+    EVSQL_PARAM_INVALID,
+
+    EVSQL_PARAM_NULL_,
+    
+    EVSQL_PARAM_BINARY,
+    EVSQL_PARAM_STRING,
+    EVSQL_PARAM_UINT16,
+    EVSQL_PARAM_UINT32,
+    EVSQL_PARAM_UINT64,
+};
+
+/*
+ * Query parameter info.
+ *
+ * Use the EVSQL_PARAM_* macros to define the value of list
+ */
+struct evsql_query_params {
+    // nonzero to get results in binary format
+    enum evsql_param_format result_fmt;
+    
+    // the list of parameters, terminated by { 0, 0 }
+    struct evsql_query_param {
+        // the param type
+        enum evsql_param_type type;
+        
+        // pointer to the raw data
+        const char *data_raw;
+        
+        // the value
+        union {
+            uint16_t uint16;
+            uint32_t uint32;
+            uint64_t uint64;
+        } data;
+
+        // the explicit length of the parameter if it's binary, zero for text.
+        // set to -1 to indicate that the value is still missing
+        ssize_t length;
+    } list[];
+};
+
+// macros for defining evsql_query_params
+#define EVSQL_PARAMS(result_fmt)            { result_fmt, 
+#define EVSQL_PARAM(typenam)                    { EVSQL_PARAM_ ## typenam, NULL }
+#define EVSQL_PARAMS_END                        { EVSQL_PARAM_INVALID, NULL } \
+                                              } // <<<
+
+/*
+ * Result type
+ */
+struct evsql_result_info {
+    struct evsql *evsql;
+    struct evsql_trans *trans;
+    
+    int error;
+
+    union evsql_result {
+        // libpq
+        PGresult *pq;
+    } result;
+};
+
+/*
+ * Callback for handling query results.
+ *
+ * The query has completed, either succesfully or unsuccesfully (nonzero .error).
+ *
+ * Use the evsql_result_* functions to manipulate the results.
+ */
+typedef void (*evsql_query_cb)(const struct evsql_result_info *res, void *arg);
+
+/*
+ * Callback for handling global-level errors.
+ *
+ * The evsql is not useable anymore.
+ *
+ * XXX: this is not actually called yet, no retry logic implemented.
+ */
+typedef void (*evsql_error_cb)(struct evsql *evsql, void *arg);
+
+/*
+ * Callback for handling transaction-level errors.
+ *
+ * The transaction is not useable anymore.
+ */
+typedef void (*evsql_trans_error_cb)(struct evsql_trans *trans, void *arg);
+
+/*
+ * The transaction is ready for use.
+ */
+typedef void (*evsql_trans_ready_cb)(struct evsql_trans *trans, void *arg);
+
+/*
+ * The transaction was commited, and should not be used anymore.
+ */
+typedef void (*evsql_trans_done_cb)(struct evsql_trans *trans, void *arg);
+
+/*
+ * Create a new PostgreSQL/libpq(evpq) -based evsql using the given conninfo.
+ *
+ * The given conninfo must stay valid for the duration of the evsql's lifetime.
+ */
+struct evsql *evsql_new_pq (struct event_base *ev_base, const char *pq_conninfo, evsql_error_cb error_fn, void *cb_arg);
+
+/*
+ * Create a new transaction.
+ *
+ * Transactions are separate connections that provide transaction-isolation.
+ *
+ * Once the transaction is ready for use, ready_fn will be called. If the transaction fails, any pending query will be
+ * forgotten, and error_fn called. This also includes some (but not all) cases where evsql_query returns nonzero.
+ *
+ */
+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);
+
+/*
+ * Queue the given query for execution.
+ *
+ * If trans is specified (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, andmay be executed immediately,
+ * or if other similar queries are running, it will be queued for later execution.
+ *
+ * Once the query is complete (got a result, got an error, the connection failed), then the query_cb will be triggered.
+ */
+struct evsql_query *evsql_query (struct evsql *evsql, struct evsql_trans *trans, const char *command, evsql_query_cb query_fn, void *cb_arg);
+
+/*
+ * Same as evsql_query, but uses the SQL-level support for binding parameters.
+ */
+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);
+
+/*
+ * Abort a query, the query callback will not be called, the query and any possible results will be discarded.
+ *
+ * This does not garuntee that the query will not execute, simply that you won't get the results.
+ *
+ * If the query is part of a transaction, then trans must be given, and the query must be the query that is currently
+ * executing on that trans. The transaction's ready_fn will be called once the query has been aborted.
+ */
+void evsql_query_abort (struct evsql_trans *trans, struct evsql_query *query);
+
+/*
+ * Commit a transaction, calling done_fn if it was succesfull (error_fn otherwise).
+ *
+ * trans must be idle, just like for evsql_query.
+ *
+ * done_fn will never be called directly, always via the event loop.
+ *
+ * You cannot abort a COMMIT, calling trans_abort on trans after a succesful trans_commit is a FATAL error.
+ */
+int evsql_trans_commit (struct evsql_trans *trans);
+
+/*
+ * Abort a transaction, rolling it back. No callbacks will be called.
+ *
+ * You cannot abort a COMMIT, calling trans_abort on trans after a succesful trans_commit is a FATAL error.
+ */
+void evsql_trans_abort (struct evsql_trans *trans);
+
+/*
+ * Transaction-handling functions
+ */
+
+// error string, meant to be called from evsql_trans_error_cb
+const char *evsql_trans_error (struct evsql_trans *trans);
+
+/*
+ * Param-building functions
+ */
+int evsql_param_binary (struct evsql_query_params *params, size_t param, const char *ptr, size_t len);
+int evsql_param_string (struct evsql_query_params *params, size_t param, const char *ptr);
+int evsql_param_uint16 (struct evsql_query_params *params, size_t param, uint16_t uval);
+int evsql_param_uint32 (struct evsql_query_params *params, size_t param, uint32_t uval);
+int evsql_param_null   (struct evsql_query_params *params, size_t param);
+int evsql_params_clear (struct evsql_query_params *params);
+
+/*
+ * Query-handling functions
+ */
+
+// print out a textual repr of the given query/params via DEBUG
+void evsql_query_debug (const char *sql, const struct evsql_query_params *params);
+
+/*
+ * Result-handling functions
+ */
+
+// get error message associated with function
+const char *evsql_result_error (const struct evsql_result_info *res);
+
+// number of rows in the result
+size_t evsql_result_rows (const struct evsql_result_info *res);
+
+// number of columns in the result
+size_t evsql_result_cols (const struct evsql_result_info *res);
+
+// number of affected rows for UPDATE/INSERT
+size_t evsql_result_affected (const struct evsql_result_info *res);
+
+// fetch the raw binary value from a result set, and return it via ptr
+// if size is nonzero, check that the size of the field data matches
+int evsql_result_binary (const struct evsql_result_info *res, size_t row, size_t col, const char **ptr, size_t *size, int nullok);
+int evsql_result_string (const struct evsql_result_info *res, size_t row, size_t col, const char **ptr, int nullok);
+
+// fetch certain kinds of values from a binary result set
+int evsql_result_uint16 (const struct evsql_result_info *res, size_t row, size_t col, uint16_t *uval, int nullok);
+int evsql_result_uint32 (const struct evsql_result_info *res, size_t row, size_t col, uint32_t *uval, int nullok);
+int evsql_result_uint64 (const struct evsql_result_info *res, size_t row, size_t col, uint64_t *uval, int nullok);
+
+// release the result set, freeing its memory
+void evsql_result_free (const struct evsql_result_info *res);
+
+// platform-dependant aliases
+#define evsql_result_ushort evsql_result_uint16
+
+#if _LP64
+#define evsql_result_ulong evsql_result_uint64
+#else
+#define evsql_result_ulong evsql_result_uint32
+#endif /* _LP64 */
+
+/*
+ * Close a connection. Callbacks for waiting queries will not be run.
+ *
+ * XXX: not implemented yet.
+ */
+void evsql_close (struct evsql *evsql);
+
+#endif /* EVSQL_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/core.c	Sun Mar 08 00:19:12 2009 +0200
@@ -0,0 +1,1025 @@
+#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;
+}
+
+/*
+ * 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).
+ */
+static 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, const struct evsql_result_info *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
+    _evsql_query_free(query);
+}
+
+/*
+ * XXX:
+ * /
+static void _evsql_destroy (struct evsql *evsql, const struct evsql_result_info *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_info res; ZINIT(res);
+    
+    // set up the result_info
+    res.evsql = evsql;
+    res.trans = NULL;
+    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) {
+        // 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 (const struct evsql_result_info *res, void *arg) {
+    (void) arg;
+
+    assert(res->trans);
+
+    // check for errors
+    if (res->error)
+        ERROR("transaction 'BEGIN' failed: %s", evsql_result_error(res));
+    
+    // transaction is now ready for use
+    res->trans->ready_fn(res->trans, res->trans->cb_arg);
+    
+    // good
+    return;
+
+error:
+    _evsql_trans_fail(res->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, NULL) == 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.evpq) {
+        WARNING("[evsql] evpq query returned multiple results, discarding previous one");
+        
+        PQclear(query->result.evpq); query->result.evpq = NULL;
+    }
+    
+    // remember the result
+    query->result.evpq = 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_info res; ZINIT(res);
+    
+    assert(query != NULL);
+    
+    // set up the result_info
+    res.evsql = conn->evsql;
+    res.trans = conn->trans;
+    
+    if (query->result.evpq == 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.evpq), "") != 0) {
+        // the query failed with some error
+        res.error = 1;
+        res.result.pq = query->result.evpq;
+
+    } else {
+        res.error = 0;
+        res.result.pq = query->result.evpq;
+
+    }
+
+    // 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;
+}
+
+/*
+ * Validate and allocate the basic stuff for a new query.
+ */
+static 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;
+}
+
+/*
+ * Handle a new query.
+ *
+ * For transactions this will associate the query and then execute it, otherwise this will either find an idle
+ * connection and send the query, or enqueue it.
+ */
+static 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;
+}
+
+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_query_param *param;
+    int 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->type; param++) 
+        query->params.count++;
+
+    // allocate the vertical storage for the parameters
+    if (0
+        
+        ||  !(query->params.types    = calloc(query->params.count, sizeof(Oid)))
+        ||  !(query->params.values   = calloc(query->params.count, sizeof(char *)))
+        ||  !(query->params.lengths  = calloc(query->params.count, sizeof(int)))
+        ||  !(query->params.formats  = calloc(query->params.count, sizeof(int)))
+    )
+        ERROR("calloc");
+
+    // transform
+    for (param = params->list, idx = 0; param->type; param++, idx++) {
+        // `set for NULLs, otherwise not
+        query->params.types[idx] = param->data_raw ? 0 : EVSQL_PQ_ARBITRARY_TYPE_OID;
+        
+        // values
+        query->params.values[idx] = param->data_raw;
+
+        // lengths
+        query->params.lengths[idx] = param->length;
+
+        // formats, binary if length is nonzero, but text for NULLs
+        query->params.formats[idx] = param->length && param->data_raw ? 1 : 0;
+    }
+
+    // result format
+    switch (params->result_fmt) {
+        case EVSQL_FMT_TEXT:
+            query->params.result_format = 0; break;
+
+        case EVSQL_FMT_BINARY:
+            query->params.result_format = 1; break;
+
+        default:
+            FATAL("params.result_fmt: %d", params->result_fmt);
+    }
+
+    // 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;
+}
+
+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;
+}
+
+void _evsql_trans_commit_res (const struct evsql_result_info *res, void *arg) {
+    (void) arg;
+
+    assert(res->trans);
+
+    // check for errors
+    if (res->error)
+        ERROR("transaction 'COMMIT' failed: %s", evsql_result_error(res));
+    
+    // transaction is now done
+    res->trans->done_fn(res->trans, res->trans->cb_arg);
+    
+    // release it
+    _evsql_trans_release(res->trans);
+
+    // success
+    return;
+
+error:
+    _evsql_trans_fail(res->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, NULL) == 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 (const struct evsql_result_info *res, void *arg) {
+    (void) arg;
+
+    assert(res->trans);
+
+    // fail the connection on errors
+    if (res->error)
+        ERROR("transaction 'ROLLBACK' failed: %s", evsql_result_error(res));
+
+    // release it
+    _evsql_trans_release(res->trans);
+
+    // success
+    return;
+
+error:
+    // fail the connection too, errors are supressed
+    _evsql_trans_fail(res->trans);
+}
+
+/*
+ * Used as the ready_fn callback in case of abort, otherwise directly
+ */
+void _evsql_trans_rollback (struct evsql_trans *trans, void *unused) {
+    static const char *sql = "ROLLBACK TRANSACTION";
+
+    (void) unused;
+
+    // query
+    if (evsql_query(trans->evsql, trans, sql, _evsql_trans_rollback_res, NULL) == NULL) {
+        // fail the transaction/connection
+        _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
+        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	Tue Nov 18 02:06:52 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	Tue Nov 18 02:06:52 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	Tue Nov 18 02:06:52 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,178 +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 (const struct evsql_result_info *res, void *arg) {
-    struct fuse_req *req = arg;
-    struct stat st; ZINIT(st);
-    int err = 0;
-    
-    uint32_t ino;
-
-    // check the results
-    if ((err = _dbfs_check_res(res, 1, 5)))
-        SERROR(err = (err ==  1 ? ENOENT : EIO));
-        
-    // get our data
-    if (0
-        ||  evsql_result_uint32(res, 0, 0, &ino,        0 ) // inodes.ino
-    )
-        EERROR(err = EIO, "invalid db data");
- 
-        
-    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, res, 0, 1)))
-        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	Tue Nov 18 02:06:52 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,114 +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 (const struct evsql_result_info *res, size_t rows, size_t cols) {
-    int err = 0;
-
-    // check if it failed
-    if (res->error)
-        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 (const struct evsql_result_info *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 (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");
-    
-    // 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, const struct evsql_result_info *res, size_t row, size_t col_offset) {
-    int err = 0;
-    
-    uint16_t mode;
-    uint32_t size = 0;  // NULL for non-REG inodes
-    uint64_t nlink = 0; // will be NULL for non-GROUP BY queries
-    const char *type;
-    
-    // extract the data
-    if ((0
-        ||  evsql_result_string(res, row, col_offset + 0, &type,       0 ) // inodes.type
-        ||  evsql_result_uint16(res, row, col_offset + 1, &mode,       0 ) // inodes.mode
-        ||  evsql_result_uint32(res, row, col_offset + 2, &size,       1 ) // size
-        ||  evsql_result_uint64(res, row, col_offset + 3, &nlink,      1 ) // count(*)
-    ) && (err = EIO))
-        ERROR("invalid db data");
-
-    INFO("\tst_mode=S_IF%s | %ho, st_nlink=%llu, st_size=%llu", type, mode, (long long unsigned int) nlink, (long long unsigned int) size);
-
-    // convert and store
-    st->st_mode = _dbfs_mode(type) | mode;
-    st->st_nlink = nlink;
-    st->st_size = size;
-    
-    // good
-    return 0;
-
-error:
-    return err;
-}
-
-
-
--- a/src/dbfs/dbfs.c	Tue Nov 18 02:06:52 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	Tue Nov 18 02:06:52 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,109 +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
-
-// 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"
-
-/*
- * 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 (const struct evsql_result_info *res, size_t rows, size_t cols);
-
-/*
- * Same as _dbfs_check_res, but returns ENOENT/EIO directly
- */
-err_t dbfs_check_result (const struct evsql_result_info *res, size_t rows, size_t cols);
-
-/*
- * 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, const struct evsql_result_info *res, size_t row, size_t col_offset);
-
-/** 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	Tue Nov 18 02:06:52 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 (const struct evsql_result_info *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 (const struct evsql_result_info *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	Tue Nov 18 02:06:52 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 (const struct evsql_result_info *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 (const struct evsql_result_info *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 (const struct evsql_result_info *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	Tue Nov 18 02:06:52 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,53 +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);
-    
-    /*
-     * 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);
-}
--- a/src/dbfs/link.c	Tue Nov 18 02:06:52 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,279 +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 (const struct evsql_result_info *res, void *arg) {
-    struct fuse_req *req = arg;
-    struct fuse_entry_param e; ZINIT(e);
-    int err = 0;
-    
-    uint32_t ino;
-    
-    // check the results
-    if ((err = _dbfs_check_res(res, 1, 5)))
-        SERROR(err = (err ==  1 ? ENOENT : EIO));
-    
-    // get the data
-    if (0
-        ||  evsql_result_uint32(res, 0, 0, &ino,        0 ) // inodes.ino
-    )
-        EERROR(err = EIO, "invalid db data");
-        
-    INFO("\t[dbfs.lookup] -> ino=%u", ino);
-    
-    // stat attrs
-    if ((err = _dbfs_stat_info(&e.attr, res, 0, 1)))
-        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");
-
-    // free
-    evsql_result_free(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 and params
-    const char *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";
-    
-    static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) {
-        EVSQL_PARAM ( UINT32 ),
-        EVSQL_PARAM ( STRING ),
-
-        EVSQL_PARAMS_END
-    };
-    
-    // build params
-    if (0
-        ||  evsql_param_uint32(&params, 0, parent)
-        ||  evsql_param_string(&params, 1, name)
-    )
-        EERROR(err = EIO, "evsql_param_*");
-
-    // query
-    if ((query = evsql_query_params(ctx->db, NULL, sql, &params, dbfs_entry_res, req)) == 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 (const struct evsql_result_info *res, void *arg) {
-    struct fuse_req *req = arg;
-    int err = 0;
-    
-    uint32_t ino;
-    const char *type, *link;
-
-    // check the results
-    if ((err = _dbfs_check_res(res, 1, 3)))
-        SERROR(err = (err ==  1 ? ENOENT : EIO));
-        
-    // get our data
-    if (0
-        ||  evsql_result_uint32(res, 0, 0, &ino,        0 ) // inodes.ino
-        ||  evsql_result_string(res, 0, 1, &type,       0 ) // inodes.type
-        ||  evsql_result_string(res, 0, 2, &link,       1 ) // inodes.link_path
-    )
-        EERROR(err = EIO, "invalid db data");
-    
-    // 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);
-
-    const char *sql =
-        "SELECT"
-        " inodes.ino, inodes.type, inodes.link_path"
-        " 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_readlink_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");
-
-}
-
-#define SETERR(err_var, err_val, bool_val) ((err_var) = bool_val ? (err_val) : 0)
-
-void dbfs_unlink_res (const struct evsql_result_info *res, void *arg) {
-    struct fuse_req *req = arg;
-    int err = 0;
-    
-    // check the results
-    // XXX: reply with ENOTEMPTY if it fails due to this inode being a dir
-    if ((err = dbfs_check_result(res, 1, 0)))
-        goto error;
-        
-    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);
-
-    const char *sql =
-        "DELETE"
-        " FROM file_tree"
-        " WHERE parent = $1::int4 AND name = $2::varchar";
-
-    static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) {
-        EVSQL_PARAM ( UINT32 ),
-        EVSQL_PARAM ( STRING ),
-
-        EVSQL_PARAMS_END
-    };
-
-    // build params
-    if (0
-        ||  evsql_param_uint32(&params, 0, parent)
-        ||  evsql_param_string(&params, 1, name)
-    )
-        SERROR(err = EIO);
-        
-    // query
-    if ((query = evsql_query_params(ctx->db, NULL, sql, &params, dbfs_unlink_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_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);
-
-    const char *sql =
-        "SELECT ino, type, mode, size, nlink FROM dbfs_link($1::int4, $2::int4, $3::varchar)";
-
-    static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) {
-        EVSQL_PARAM ( UINT32 ),
-        EVSQL_PARAM ( UINT32 ),
-        EVSQL_PARAM ( STRING ),
-
-        EVSQL_PARAMS_END
-    };
-
-    // build params
-    if (0
-        ||  evsql_param_uint32(&params, 0, ino)
-        ||  evsql_param_uint32(&params, 1, newparent)
-        ||  evsql_param_string(&params, 2, newname)
-    )
-        SERROR(err = EIO);
-        
-    // query
-    if ((query = evsql_query_params(ctx->db, NULL, sql, &params, dbfs_entry_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/mk.c	Tue Nov 18 02:06:52 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 (const struct evsql_result_info *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 (const struct evsql_result_info *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	Tue Nov 18 02:06:52 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	Tue Nov 18 02:06:52 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	Tue Nov 18 02:06:52 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	Tue Nov 18 02:06:52 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	Tue Nov 18 02:06:52 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	Tue Nov 18 02:06:52 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 (const struct evsql_result_info *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	Tue Nov 18 02:06:52 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	Tue Nov 18 02:06:52 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	Tue Nov 18 02:06:52 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	Tue Nov 18 02:06:52 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	Tue Nov 18 02:06:52 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,276 +0,0 @@
-#ifndef EVSQL_H
-#define EVSQL_H
-
-/*
- * An event-based (Postgre)SQL client API using libevent
- */
-
-// XXX: libpq
-#include <stdint.h>
-#include <postgresql/libpq-fe.h>
-#include <event2/event.h>
-
-/*
- * The generic context handle
- */
-struct evsql;
-
-/*
- * A query handle
- */
-struct evsql_query;
-
-/*
- * Transaction handle
- */
-struct evsql_trans;
-
-/*
- * Transaction type
- */
-enum evsql_trans_type {
-    EVSQL_TRANS_DEFAULT,
-    EVSQL_TRANS_SERIALIZABLE,
-    EVSQL_TRANS_REPEATABLE_READ,
-    EVSQL_TRANS_READ_COMMITTED,
-    EVSQL_TRANS_READ_UNCOMMITTED,
-};
-
-/*
- * Parameter type
- */
-enum evsql_param_format {
-    EVSQL_FMT_TEXT,
-    EVSQL_FMT_BINARY,
-};
-
-enum evsql_param_type {
-    EVSQL_PARAM_INVALID,
-
-    EVSQL_PARAM_NULL_,
-    
-    EVSQL_PARAM_BINARY,
-    EVSQL_PARAM_STRING,
-    EVSQL_PARAM_UINT16,
-    EVSQL_PARAM_UINT32,
-    EVSQL_PARAM_UINT64,
-};
-
-/*
- * Query parameter info.
- *
- * Use the EVSQL_PARAM_* macros to define the value of list
- */
-struct evsql_query_params {
-    // nonzero to get results in binary format
-    enum evsql_param_format result_fmt;
-    
-    // the list of parameters, terminated by { 0, 0 }
-    struct evsql_query_param {
-        // the param type
-        enum evsql_param_type type;
-        
-        // pointer to the raw data
-        const char *data_raw;
-        
-        // the value
-        union {
-            uint16_t uint16;
-            uint32_t uint32;
-            uint64_t uint64;
-        } data;
-
-        // the explicit length of the parameter if it's binary, zero for text.
-        // set to -1 to indicate that the value is still missing
-        ssize_t length;
-    } list[];
-};
-
-// macros for defining evsql_query_params
-#define EVSQL_PARAMS(result_fmt)            { result_fmt, 
-#define EVSQL_PARAM(typenam)                    { EVSQL_PARAM_ ## typenam, NULL }
-#define EVSQL_PARAMS_END                        { EVSQL_PARAM_INVALID, NULL } \
-                                              } // <<<
-
-/*
- * Result type
- */
-struct evsql_result_info {
-    struct evsql *evsql;
-    struct evsql_trans *trans;
-    
-    int error;
-
-    union evsql_result {
-        // libpq
-        PGresult *pq;
-    } result;
-};
-
-/*
- * Callback for handling query results.
- *
- * The query has completed, either succesfully or unsuccesfully (nonzero .error).
- *
- * Use the evsql_result_* functions to manipulate the results.
- */
-typedef void (*evsql_query_cb)(const struct evsql_result_info *res, void *arg);
-
-/*
- * Callback for handling global-level errors.
- *
- * The evsql is not useable anymore.
- *
- * XXX: this is not actually called yet, no retry logic implemented.
- */
-typedef void (*evsql_error_cb)(struct evsql *evsql, void *arg);
-
-/*
- * Callback for handling transaction-level errors.
- *
- * The transaction is not useable anymore.
- */
-typedef void (*evsql_trans_error_cb)(struct evsql_trans *trans, void *arg);
-
-/*
- * The transaction is ready for use.
- */
-typedef void (*evsql_trans_ready_cb)(struct evsql_trans *trans, void *arg);
-
-/*
- * The transaction was commited, and should not be used anymore.
- */
-typedef void (*evsql_trans_done_cb)(struct evsql_trans *trans, void *arg);
-
-/*
- * Create a new PostgreSQL/libpq(evpq) -based evsql using the given conninfo.
- *
- * The given conninfo must stay valid for the duration of the evsql's lifetime.
- */
-struct evsql *evsql_new_pq (struct event_base *ev_base, const char *pq_conninfo, evsql_error_cb error_fn, void *cb_arg);
-
-/*
- * Create a new transaction.
- *
- * Transactions are separate connections that provide transaction-isolation.
- *
- * Once the transaction is ready for use, ready_fn will be called. If the transaction fails, any pending query will be
- * forgotten, and error_fn called. This also includes some (but not all) cases where evsql_query returns nonzero.
- *
- */
-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);
-
-/*
- * Queue the given query for execution.
- *
- * If trans is specified (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, andmay be executed immediately,
- * or if other similar queries are running, it will be queued for later execution.
- *
- * Once the query is complete (got a result, got an error, the connection failed), then the query_cb will be triggered.
- */
-struct evsql_query *evsql_query (struct evsql *evsql, struct evsql_trans *trans, const char *command, evsql_query_cb query_fn, void *cb_arg);
-
-/*
- * Same as evsql_query, but uses the SQL-level support for binding parameters.
- */
-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);
-
-/*
- * Abort a query, the query callback will not be called, the query and any possible results will be discarded.
- *
- * This does not garuntee that the query will not execute, simply that you won't get the results.
- *
- * If the query is part of a transaction, then trans must be given, and the query must be the query that is currently
- * executing on that trans. The transaction's ready_fn will be called once the query has been aborted.
- */
-void evsql_query_abort (struct evsql_trans *trans, struct evsql_query *query);
-
-/*
- * Commit a transaction, calling done_fn if it was succesfull (error_fn otherwise).
- *
- * trans must be idle, just like for evsql_query.
- *
- * done_fn will never be called directly, always via the event loop.
- *
- * You cannot abort a COMMIT, calling trans_abort on trans after a succesful trans_commit is a FATAL error.
- */
-int evsql_trans_commit (struct evsql_trans *trans);
-
-/*
- * Abort a transaction, rolling it back. No callbacks will be called.
- *
- * You cannot abort a COMMIT, calling trans_abort on trans after a succesful trans_commit is a FATAL error.
- */
-void evsql_trans_abort (struct evsql_trans *trans);
-
-/*
- * Transaction-handling functions
- */
-
-// error string, meant to be called from evsql_trans_error_cb
-const char *evsql_trans_error (struct evsql_trans *trans);
-
-/*
- * Param-building functions
- */
-int evsql_param_binary (struct evsql_query_params *params, size_t param, const char *ptr, size_t len);
-int evsql_param_string (struct evsql_query_params *params, size_t param, const char *ptr);
-int evsql_param_uint16 (struct evsql_query_params *params, size_t param, uint16_t uval);
-int evsql_param_uint32 (struct evsql_query_params *params, size_t param, uint32_t uval);
-int evsql_param_null   (struct evsql_query_params *params, size_t param);
-int evsql_params_clear (struct evsql_query_params *params);
-
-/*
- * Query-handling functions
- */
-
-// print out a textual repr of the given query/params via DEBUG
-void evsql_query_debug (const char *sql, const struct evsql_query_params *params);
-
-/*
- * Result-handling functions
- */
-
-// get error message associated with function
-const char *evsql_result_error (const struct evsql_result_info *res);
-
-// number of rows in the result
-size_t evsql_result_rows (const struct evsql_result_info *res);
-
-// number of columns in the result
-size_t evsql_result_cols (const struct evsql_result_info *res);
-
-// number of affected rows for UPDATE/INSERT
-size_t evsql_result_affected (const struct evsql_result_info *res);
-
-// fetch the raw binary value from a result set, and return it via ptr
-// if size is nonzero, check that the size of the field data matches
-int evsql_result_binary (const struct evsql_result_info *res, size_t row, size_t col, const char **ptr, size_t *size, int nullok);
-int evsql_result_string (const struct evsql_result_info *res, size_t row, size_t col, const char **ptr, int nullok);
-
-// fetch certain kinds of values from a binary result set
-int evsql_result_uint16 (const struct evsql_result_info *res, size_t row, size_t col, uint16_t *uval, int nullok);
-int evsql_result_uint32 (const struct evsql_result_info *res, size_t row, size_t col, uint32_t *uval, int nullok);
-int evsql_result_uint64 (const struct evsql_result_info *res, size_t row, size_t col, uint64_t *uval, int nullok);
-
-// release the result set, freeing its memory
-void evsql_result_free (const struct evsql_result_info *res);
-
-// platform-dependant aliases
-#define evsql_result_ushort evsql_result_uint16
-
-#if _LP64
-#define evsql_result_ulong evsql_result_uint64
-#else
-#define evsql_result_ulong evsql_result_uint32
-#endif /* _LP64 */
-
-/*
- * Close a connection. Callbacks for waiting queries will not be run.
- *
- * XXX: not implemented yet.
- */
-void evsql_close (struct evsql *evsql);
-
-#endif /* EVSQL_H */
--- a/src/evsql/evsql.c	Tue Nov 18 02:06:52 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1025 +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;
-}
-
-/*
- * 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).
- */
-static 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, const struct evsql_result_info *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
-    _evsql_query_free(query);
-}
-
-/*
- * XXX:
- * /
-static void _evsql_destroy (struct evsql *evsql, const struct evsql_result_info *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_info res; ZINIT(res);
-    
-    // set up the result_info
-    res.evsql = evsql;
-    res.trans = NULL;
-    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) {
-        // 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 (const struct evsql_result_info *res, void *arg) {
-    (void) arg;
-
-    assert(res->trans);
-
-    // check for errors
-    if (res->error)
-        ERROR("transaction 'BEGIN' failed: %s", evsql_result_error(res));
-    
-    // transaction is now ready for use
-    res->trans->ready_fn(res->trans, res->trans->cb_arg);
-    
-    // good
-    return;
-
-error:
-    _evsql_trans_fail(res->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, NULL) == 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.evpq) {
-        WARNING("[evsql] evpq query returned multiple results, discarding previous one");
-        
-        PQclear(query->result.evpq); query->result.evpq = NULL;
-    }
-    
-    // remember the result
-    query->result.evpq = 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_info res; ZINIT(res);
-    
-    assert(query != NULL);
-    
-    // set up the result_info
-    res.evsql = conn->evsql;
-    res.trans = conn->trans;
-    
-    if (query->result.evpq == 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.evpq), "") != 0) {
-        // the query failed with some error
-        res.error = 1;
-        res.result.pq = query->result.evpq;
-
-    } else {
-        res.error = 0;
-        res.result.pq = query->result.evpq;
-
-    }
-
-    // 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;
-}
-
-/*
- * Validate and allocate the basic stuff for a new query.
- */
-static 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;
-}
-
-/*
- * Handle a new query.
- *
- * For transactions this will associate the query and then execute it, otherwise this will either find an idle
- * connection and send the query, or enqueue it.
- */
-static 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;
-}
-
-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_query_param *param;
-    int 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->type; param++) 
-        query->params.count++;
-
-    // allocate the vertical storage for the parameters
-    if (0
-        
-        ||  !(query->params.types    = calloc(query->params.count, sizeof(Oid)))
-        ||  !(query->params.values   = calloc(query->params.count, sizeof(char *)))
-        ||  !(query->params.lengths  = calloc(query->params.count, sizeof(int)))
-        ||  !(query->params.formats  = calloc(query->params.count, sizeof(int)))
-    )
-        ERROR("calloc");
-
-    // transform
-    for (param = params->list, idx = 0; param->type; param++, idx++) {
-        // `set for NULLs, otherwise not
-        query->params.types[idx] = param->data_raw ? 0 : EVSQL_PQ_ARBITRARY_TYPE_OID;
-        
-        // values
-        query->params.values[idx] = param->data_raw;
-
-        // lengths
-        query->params.lengths[idx] = param->length;
-
-        // formats, binary if length is nonzero, but text for NULLs
-        query->params.formats[idx] = param->length && param->data_raw ? 1 : 0;
-    }
-
-    // result format
-    switch (params->result_fmt) {
-        case EVSQL_FMT_TEXT:
-            query->params.result_format = 0; break;
-
-        case EVSQL_FMT_BINARY:
-            query->params.result_format = 1; break;
-
-        default:
-            FATAL("params.result_fmt: %d", params->result_fmt);
-    }
-
-    // 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;
-}
-
-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;
-}
-
-void _evsql_trans_commit_res (const struct evsql_result_info *res, void *arg) {
-    (void) arg;
-
-    assert(res->trans);
-
-    // check for errors
-    if (res->error)
-        ERROR("transaction 'COMMIT' failed: %s", evsql_result_error(res));
-    
-    // transaction is now done
-    res->trans->done_fn(res->trans, res->trans->cb_arg);
-    
-    // release it
-    _evsql_trans_release(res->trans);
-
-    // success
-    return;
-
-error:
-    _evsql_trans_fail(res->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, NULL) == 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 (const struct evsql_result_info *res, void *arg) {
-    (void) arg;
-
-    assert(res->trans);
-
-    // fail the connection on errors
-    if (res->error)
-        ERROR("transaction 'ROLLBACK' failed: %s", evsql_result_error(res));
-
-    // release it
-    _evsql_trans_release(res->trans);
-
-    // success
-    return;
-
-error:
-    // fail the connection too, errors are supressed
-    _evsql_trans_fail(res->trans);
-}
-
-/*
- * Used as the ready_fn callback in case of abort, otherwise directly
- */
-void _evsql_trans_rollback (struct evsql_trans *trans, void *unused) {
-    static const char *sql = "ROLLBACK TRANSACTION";
-
-    (void) unused;
-
-    // query
-    if (evsql_query(trans->evsql, trans, sql, _evsql_trans_rollback_res, NULL) == NULL) {
-        // fail the transaction/connection
-        _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
-        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	Tue Nov 18 02:06:52 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,140 +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;
-
-};
-
-/*
- * 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_param_info {
-        int count;
-
-        Oid *types;
-        const char **values;
-        int *lengths;
-        int *formats;
-
-        int result_format;
-    } params;
-
-    // our callback
-    evsql_query_cb cb_fn;
-    void *cb_arg;
-
-    // our position in the query list
-    TAILQ_ENTRY(evsql_query) entry;
-
-    // the result
-    union {
-        PGresult *evpq;
-    } result;
-};
-
-// 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
-
-#endif /* EVSQL_INTERNAL_H */
--- a/src/evsql/util.c	Tue Nov 18 02:06:52 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,331 +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_PARAM_ ## typenam: return #typenam
-
-#define _PARAM_VAL_BUF_MAX 120
-#define _PARAM_VAL_CASE(typenam, ...) case EVSQL_PARAM_ ## typenam: if (param->data_raw) ret = snprintf(buf, _PARAM_VAL_BUF_MAX, __VA_ARGS__); else return "(null)"; break
-
-const char *evsql_param_type (const struct evsql_query_param *param) {
-    switch (param->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_param_val (const struct evsql_query_param *param) {
-    static char buf[_PARAM_VAL_BUF_MAX];
-    int ret;
-
-    switch (param->type) {
-        _PARAM_VAL_CASE (INVALID,   "???"                               );
-        _PARAM_VAL_CASE (NULL_,     "(null)"                            );
-        _PARAM_VAL_CASE (BINARY,    "%zu:%s",   param->length, "..."    );
-        _PARAM_VAL_CASE (STRING,    "%s",       param->data_raw         );
-        _PARAM_VAL_CASE (UINT16,    "%hu",      (unsigned short int)     ntohs(param->data.uint16)  );
-        _PARAM_VAL_CASE (UINT32,    "%lu",      (unsigned long int)      ntohl(param->data.uint32)  );
-        _PARAM_VAL_CASE (UINT64,    "%llu",     (unsigned long long int) ntohq(param->data.uint64)  );
-        default: return "???";
-    }
-
-    return buf;
-}
-
-int evsql_params_clear (struct evsql_query_params *params) {
-    struct evsql_query_param *param;
-
-    for (param = params->list; param->type; param++) 
-        param->data_raw = NULL;
-
-    return 0;
-}
-
-int evsql_param_null   (struct evsql_query_params *params, size_t param) {
-    struct evsql_query_param *p = &params->list[param];
-
-    p->data_raw = NULL;
-
-    return 0;
-}
-
-int evsql_param_binary (struct evsql_query_params *params, size_t param, const char *ptr, size_t len) {
-    struct evsql_query_param *p = &params->list[param];
-    
-    assert(p->type == EVSQL_PARAM_BINARY);
-
-    p->data_raw = ptr;
-    p->length = len;
-
-    return 0;
-}
-
-int evsql_param_string (struct evsql_query_params *params, size_t param, const char *ptr) {
-    struct evsql_query_param *p = &params->list[param];
-    
-    assert(p->type == EVSQL_PARAM_STRING);
-
-    p->data_raw = ptr;
-    p->length = 0;
-
-    return 0;
-}
-
-int evsql_param_uint16 (struct evsql_query_params *params, size_t param, uint16_t uval) {
-    struct evsql_query_param *p = &params->list[param];
-    
-    assert(p->type == EVSQL_PARAM_UINT16);
-
-    p->data.uint16 = htons(uval);
-    p->data_raw = (const char *) &p->data.uint16;
-    p->length = sizeof(uval);
-
-    return 0;
-}
-
-int evsql_param_uint32 (struct evsql_query_params *params, size_t param, uint32_t uval) {
-    struct evsql_query_param *p = &params->list[param];
-    
-    assert(p->type == EVSQL_PARAM_UINT32);
-
-    p->data.uint32 = htonl(uval);
-    p->data_raw = (const char *) &p->data.uint32;
-    p->length = sizeof(uval);
-
-    return 0;
-}
-
-void evsql_query_debug (const char *sql, const struct evsql_query_params *params) {
-    const struct evsql_query_param *param;
-    size_t param_count = 0, idx = 0;
-
-    // count the params
-    for (param = params->list; param->type; param++) 
-        param_count++;
-    
-    DEBUG("sql:     %s", sql);
-    DEBUG("params:  %zu", param_count);
-
-    for (param = params->list; param->type; param++) {
-        DEBUG("\t%2zu : %8s = %s", ++idx, evsql_param_type(param), evsql_param_val(param));
-    }
-}
-
-const char *evsql_result_error (const struct evsql_result_info *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_info *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_info *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_info *res) {
-    switch (res->evsql->type) {
-        case EVSQL_EVPQ:
-            return strtol(PQcmdTuples(res->result.pq), NULL, 10);
-
-        default:
-            FATAL("res->evsql->type");
-    }
-}
-
-int evsql_result_binary (const struct evsql_result_info *res, size_t row, size_t col, const char **ptr, size_t *size, int 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_info *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_info *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_info *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_info *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_info *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;
-}
-
-void evsql_result_free (const struct evsql_result_info *res) {
-    switch (res->evsql->type) {
-        case EVSQL_EVPQ:
-            return PQclear(res->result.pq);
-
-        default:
-            FATAL("res->evsql->type");
-    }
-}
-
-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	Tue Nov 18 02:06:52 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	Tue Nov 18 02:06:52 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 00:19:12 2009 +0200
@@ -0,0 +1,140 @@
+#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;
+
+};
+
+/*
+ * 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_param_info {
+        int count;
+
+        Oid *types;
+        const char **values;
+        int *lengths;
+        int *formats;
+
+        int result_format;
+    } params;
+
+    // our callback
+    evsql_query_cb cb_fn;
+    void *cb_arg;
+
+    // our position in the query list
+    TAILQ_ENTRY(evsql_query) entry;
+
+    // the result
+    union {
+        PGresult *evpq;
+    } result;
+};
+
+// 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
+
+#endif /* EVSQL_INTERNAL_H */
--- a/src/lib/lex.c	Tue Nov 18 02:06:52 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	Tue Nov 18 02:06:52 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	Tue Nov 18 02:06:52 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	Tue Nov 18 02:06:52 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	Tue Nov 18 02:06:52 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	Tue Nov 18 02:06:52 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 */
--- a/src/simple.c	Tue Nov 18 02:06:52 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	Tue Nov 18 02:06:52 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	Tue Nov 18 02:06:52 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	Tue Nov 18 02:06:52 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 00:19:12 2009 +0200
@@ -0,0 +1,331 @@
+#include <stdlib.h>
+#include <assert.h>
+
+#include "internal.h"
+#include "lib/log.h"
+#include "lib/misc.h"
+
+#define _PARAM_TYPE_CASE(typenam) case EVSQL_PARAM_ ## typenam: return #typenam
+
+#define _PARAM_VAL_BUF_MAX 120
+#define _PARAM_VAL_CASE(typenam, ...) case EVSQL_PARAM_ ## typenam: if (param->data_raw) ret = snprintf(buf, _PARAM_VAL_BUF_MAX, __VA_ARGS__); else return "(null)"; break
+
+const char *evsql_param_type (const struct evsql_query_param *param) {
+    switch (param->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_param_val (const struct evsql_query_param *param) {
+    static char buf[_PARAM_VAL_BUF_MAX];
+    int ret;
+
+    switch (param->type) {
+        _PARAM_VAL_CASE (INVALID,   "???"                               );
+        _PARAM_VAL_CASE (NULL_,     "(null)"                            );
+        _PARAM_VAL_CASE (BINARY,    "%zu:%s",   param->length, "..."    );
+        _PARAM_VAL_CASE (STRING,    "%s",       param->data_raw         );
+        _PARAM_VAL_CASE (UINT16,    "%hu",      (unsigned short int)     ntohs(param->data.uint16)  );
+        _PARAM_VAL_CASE (UINT32,    "%lu",      (unsigned long int)      ntohl(param->data.uint32)  );
+        _PARAM_VAL_CASE (UINT64,    "%llu",     (unsigned long long int) ntohq(param->data.uint64)  );
+        default: return "???";
+    }
+
+    return buf;
+}
+
+int evsql_params_clear (struct evsql_query_params *params) {
+    struct evsql_query_param *param;
+
+    for (param = params->list; param->type; param++) 
+        param->data_raw = NULL;
+
+    return 0;
+}
+
+int evsql_param_null   (struct evsql_query_params *params, size_t param) {
+    struct evsql_query_param *p = &params->list[param];
+
+    p->data_raw = NULL;
+
+    return 0;
+}
+
+int evsql_param_binary (struct evsql_query_params *params, size_t param, const char *ptr, size_t len) {
+    struct evsql_query_param *p = &params->list[param];
+    
+    assert(p->type == EVSQL_PARAM_BINARY);
+
+    p->data_raw = ptr;
+    p->length = len;
+
+    return 0;
+}
+
+int evsql_param_string (struct evsql_query_params *params, size_t param, const char *ptr) {
+    struct evsql_query_param *p = &params->list[param];
+    
+    assert(p->type == EVSQL_PARAM_STRING);
+
+    p->data_raw = ptr;
+    p->length = 0;
+
+    return 0;
+}
+
+int evsql_param_uint16 (struct evsql_query_params *params, size_t param, uint16_t uval) {
+    struct evsql_query_param *p = &params->list[param];
+    
+    assert(p->type == EVSQL_PARAM_UINT16);
+
+    p->data.uint16 = htons(uval);
+    p->data_raw = (const char *) &p->data.uint16;
+    p->length = sizeof(uval);
+
+    return 0;
+}
+
+int evsql_param_uint32 (struct evsql_query_params *params, size_t param, uint32_t uval) {
+    struct evsql_query_param *p = &params->list[param];
+    
+    assert(p->type == EVSQL_PARAM_UINT32);
+
+    p->data.uint32 = htonl(uval);
+    p->data_raw = (const char *) &p->data.uint32;
+    p->length = sizeof(uval);
+
+    return 0;
+}
+
+void evsql_query_debug (const char *sql, const struct evsql_query_params *params) {
+    const struct evsql_query_param *param;
+    size_t param_count = 0, idx = 0;
+
+    // count the params
+    for (param = params->list; param->type; param++) 
+        param_count++;
+    
+    DEBUG("sql:     %s", sql);
+    DEBUG("params:  %zu", param_count);
+
+    for (param = params->list; param->type; param++) {
+        DEBUG("\t%2zu : %8s = %s", ++idx, evsql_param_type(param), evsql_param_val(param));
+    }
+}
+
+const char *evsql_result_error (const struct evsql_result_info *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_info *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_info *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_info *res) {
+    switch (res->evsql->type) {
+        case EVSQL_EVPQ:
+            return strtol(PQcmdTuples(res->result.pq), NULL, 10);
+
+        default:
+            FATAL("res->evsql->type");
+    }
+}
+
+int evsql_result_binary (const struct evsql_result_info *res, size_t row, size_t col, const char **ptr, size_t *size, int 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_info *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_info *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_info *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_info *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_info *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;
+}
+
+void evsql_result_free (const struct evsql_result_info *res) {
+    switch (res->evsql->type) {
+        case EVSQL_EVPQ:
+            return PQclear(res->result.pq);
+
+        default:
+            FATAL("res->evsql->type");
+    }
+}
+
+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);
+}
+