more work on the new evsql interface new-evsql
authorTero Marttila <terom@fixme.fi>
Thu, 20 Nov 2008 01:16:24 +0200
branchnew-evsql
changeset 44 9e76ee9729b6
parent 43 5776ace903b5
child 45 424ce5ab82fd
more work on the new evsql interface
Makefile
src/dbfs/interrupt.c
src/evsql.h
src/evsql/evsql.c
src/evsql/evsql.h
src/evsql/query.c
src/evsql/result.c
src/evsql/util.c
src/evsql_test.c
src/lib/err.h
src/lib/error.h
--- a/Makefile	Wed Nov 19 19:06:10 2008 +0200
+++ b/Makefile	Thu Nov 20 01:16:24 2008 +0200
@@ -1,5 +1,5 @@
 LIBEVENT_PATH = ../libs/libevent-dev
-LIBFUSE_PATH = ../libs/libfuse-2.7.4
+LIBFUSE_PATH = ../opt
 
 LIBRARY_PATHS = -L${LIBEVENT_PATH}/lib -L${LIBFUSE_PATH}/lib
 INCLUDE_PATHS = -I${LIBEVENT_PATH}/include -I${LIBFUSE_PATH}/include
--- a/src/dbfs/interrupt.c	Wed Nov 19 19:06:10 2008 +0200
+++ b/src/dbfs/interrupt.c	Thu Nov 20 01:16:24 2008 +0200
@@ -18,15 +18,20 @@
 
     // abort query
     evsql_query_abort(NULL, query);
+
+    // error the req
+    if ((err = -fuse_reply_err(req, EINTR)))
+        EWARNING(err, "fuse_reply_err");
     
     /*
      * Due to a locking bug in libfuse (at least 2.7.4), we can't call fuse_reply_err from the interrupt function, so we must
      * schedule after this function returns.
-     */
+     * /
     tv.tv_sec = 0;
     tv.tv_usec = 0;
     if (event_base_once(ctx->ev_base, -1, EV_TIMEOUT, _dbfs_interrupt_reply, req, &tv))
         PWARNING("event_base_once failed, dropping req reply: %p", req);
+        */
 }
 
 void _dbfs_interrupt_ctx (struct fuse_req *req, void *ctx_ptr) {
--- a/src/evsql.h	Wed Nov 19 19:06:10 2008 +0200
+++ b/src/evsql.h	Thu Nov 20 01:16:24 2008 +0200
@@ -10,6 +10,8 @@
 #include <postgresql/libpq-fe.h>
 #include <event2/event.h>
 
+#include <lib/err.h>
+
 /*
  * The generic context handle
  */
@@ -62,13 +64,34 @@
     EVSQL_TYPE_UINT64,
 };
 
-struct evsql_item {
+struct evsql_item_binary {
+    const char *ptr;
+    size_t len;
+};
+
+/*
+ * Metadata about the type of an item
+ */
+struct evsql_item_info {
     // format
     enum evsql_item_format format;
     
     // type
     enum evsql_item_type type;
 
+    // flags
+    struct evsql_item_flags {
+        uint8_t null_ok : 1;
+    } flags;
+};
+
+/*
+ * The type and value of an item
+ */
+struct evsql_item {
+    // "header"
+    struct evsql_item_info info;
+
     // pointer to the raw databytes. Set to NULL to indicate SQL-NULL
     const char *bytes;
 
@@ -85,39 +108,39 @@
 
 /*
  * Query info, similar to prepared statements
+ *
+ * Contains the literal SQL query and the types of the arguments
  */
 struct evsql_query_info {
     // the SQL query itself
     const char *sql;
 
     // the list of items
-    struct evsql_item params[];
+    struct evsql_item_info params[];
+};
+
+/*
+ * Contains the query parameter types and their values
+ */
+struct evsql_query_params {
+    // result format
+    enum evsql_item_format result_format;
+    
+    // list of params
+    struct evsql_item list[];
 };
 
 /*
  * Result info
+ *
+ * Contains the types of the result columns
  */
 struct evsql_result_info {
-    int padding;
+    // XXX: put something useful here?
+    int _unused;
 
     // the list of fields
-    struct evsql_item columns[];
-};
-
-
-/*
- * Result type
- */
-struct evsql_result_info {
-    struct evsql *evsql;
-    struct evsql_trans *trans;
-    
-    int error;
-
-    union evsql_result {
-        // libpq
-        PGresult *pq;
-    } result;
+    struct evsql_item_info columns[];
 };
 
 /*
@@ -127,7 +150,7 @@
  *
  * Use the evsql_result_* functions to manipulate the results.
  */
-typedef void (*evsql_query_cb)(const struct evsql_result_info *res, void *arg);
+typedef void (*evsql_query_cb)(struct evsql_result *res, void *arg);
 
 /*
  * Callback for handling global-level errors.
@@ -187,7 +210,19 @@
 /*
  * 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);
+struct evsql_query *evsql_query_params (struct evsql *evsql, struct evsql_trans *trans, 
+    const char *command, const struct evsql_query_params *params, 
+    evsql_query_cb query_fn, void *cb_arg
+);
+
+/*
+ * Execute the given query_info, using the parameter list in query_info to resolve the given variable arugments
+ */
+struct evsql_query *evsql_query_exec (struct evsql *evsql, struct evsql_trans *trans, 
+    const struct evsql_query_info *query_info,
+    evsql_query_cb query_fn, void *cb_arg,
+    ...
+);
 
 /*
  * Abort a query, the query callback will not be called, the query and any possible results will be discarded.
@@ -246,29 +281,38 @@
  */
 
 // get error message associated with function
-const char *evsql_result_error (const struct evsql_result_info *res);
+const char *evsql_result_error (const struct evsql_result *res);
+
+/*
+ * Iterator-based interface.
+ *
+ * Call result_begin to check for errors, then result_next to fetch rows, and finally result_end to release.
+ */
+err_t evsql_result_begin (struct evsql_result_info *info, struct evsql_result *res);
+int evsql_result_next (struct evsql_result *res, ...);
+void evsql_result_end (struct evsql_result *res);
 
 // number of rows in the result
-size_t evsql_result_rows (const struct evsql_result_info *res);
+size_t evsql_result_rows (const struct evsql_result *res);
 
 // number of columns in the result
-size_t evsql_result_cols (const struct evsql_result_info *res);
+size_t evsql_result_cols (const struct evsql_result *res);
 
 // number of affected rows for UPDATE/INSERT
-size_t evsql_result_affected (const struct evsql_result_info *res);
+size_t evsql_result_affected (const struct evsql_result *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);
+int evsql_result_binary (const struct evsql_result *res, size_t row, size_t col, const char **ptr, size_t *size, int nullok);
+int evsql_result_string (const struct evsql_result *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);
+int evsql_result_uint16 (const struct evsql_result *res, size_t row, size_t col, uint16_t *uval, int nullok);
+int evsql_result_uint32 (const struct evsql_result *res, size_t row, size_t col, uint32_t *uval, int nullok);
+int evsql_result_uint64 (const struct evsql_result *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);
+void evsql_result_free (struct evsql_result *res);
 
 /*
  * Close a connection. Callbacks for waiting queries will not be run.
--- a/src/evsql/evsql.c	Wed Nov 19 19:06:10 2008 +0200
+++ b/src/evsql/evsql.c	Thu Nov 20 01:16:24 2008 +0200
@@ -731,7 +731,7 @@
 }
 
 /*
- * Validate and allocate the basic stuff for a new query.
+ * Internal query functions
  */
 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;
@@ -755,12 +755,6 @@
     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) {
@@ -818,106 +812,6 @@
     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;
--- a/src/evsql/evsql.h	Wed Nov 19 19:06:10 2008 +0200
+++ b/src/evsql/evsql.h	Thu Nov 20 01:16:24 2008 +0200
@@ -106,7 +106,7 @@
     char *command;
     
     // possible query params
-    struct evsql_query_param_info {
+    struct evsql_query_params_pq {
         int count;
 
         Oid *types;
@@ -123,13 +123,22 @@
 
     // our position in the query list
     TAILQ_ENTRY(evsql_query) entry;
+};
 
-    // the result
+// the result
+struct evsql_result {
+    int error;
+
     union {
-        PGresult *evpq;
+        PGresult *pq;
     } result;
+
+    // result_* state
+    struct evsql_result_info *info;
+    size_t row_offset;
 };
 
+
 // maximum length for a 'BEGIN TRANSACTION ...' query
 #define EVSQL_QUERY_BEGIN_BUF 512
 
@@ -137,4 +146,30 @@
 // 16 = bool in 8.3
 #define EVSQL_PQ_ARBITRARY_TYPE_OID 16
 
+/*
+ * Core query-submission interface.
+ *
+ * This performs some error-checking on the trans, allocates the evsql_query and does some basic initialization.
+ *
+ * This does not actually enqueue the query anywhere, no reference is stored anywhere.
+ *
+ * Returns the new evsql_query on success, NULL on failure.
+ */
+static struct evsql_query *_evsql_query_new (struct evsql *evsql, struct evsql_trans *trans, evsql_query_cb query_fn, void *cb_arg);
+
+/*
+ * Begin processing the given query, which should now be fully filled out.
+ *
+ * If trans is given, it MUST be idle, and the query will be executed. Otherwise, it will either be executed directly
+ * or enqueued for future execution.
+ *
+ * Returns zero on success, nonzero on failure.
+ */
+static int _evsql_query_enqueue (struct evsql *evsql, struct evsql_trans *trans, struct evsql_query *query, const char *command);
+
+/*
+ * Validate and allocate the basic stuff for a new query.
+ */
+
+
 #endif /* EVSQL_INTERNAL_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/evsql/query.c	Thu Nov 20 01:16:24 2008 +0200
@@ -0,0 +1,242 @@
+
+#include "evsql.h"
+
+/*
+ * Initialize params->types/values/lengths/formats, params->count, params->result_format based on the given args
+ */
+static int _evsql_query_params_init_pq (struct evsql_query_params_pq *params, size_t param_count, enum evsql_item_format result_format) {
+    // set count
+    params->count = param_count;
+
+    // allocate vertical storage for the parameters
+    if (0
+        
+        ||  !(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");
+
+    // result format
+    switch (result_format) {
+        case EVSQL_FMT_TEXT:
+            params.result_format = 0; break;
+
+        case EVSQL_FMT_BINARY:
+            params.result_format = 1; break;
+
+        default:
+            FATAL("params.result_fmt: %d", result_format);
+    }
+    
+    // good
+    return 0;
+
+error:
+    return -1;
+}
+
+struct evsql_query *evsql_query (struct evsql *evsql, struct evsql_trans *trans, const char *command, evsql_query_cb query_fn, void *cb_arg) {
+    struct evsql_query *query = NULL;
+    
+    // alloc new query
+    if ((query = _evsql_query_new(evsql, trans, query_fn, cb_arg)) == NULL)
+        goto error;
+    
+    // just execute the command string directly
+    if (_evsql_query_enqueue(evsql, trans, query, command))
+        goto error;
+
+    // ok
+    return query;
+
+error:
+    _evsql_query_free(query);
+
+    return NULL;
+}
+
+struct evsql_query *evsql_query_params (struct evsql *evsql, struct evsql_trans *trans, 
+    const char *command, const struct evsql_query_params *params, 
+    evsql_query_cb query_fn, void *cb_arg
+) {
+    struct evsql_query *query = NULL;
+    const struct evsql_item *param;
+    size_t count = 0, idx;
+    
+    // alloc new query
+    if ((query = _evsql_query_new(evsql, trans, query_fn, cb_arg)) == NULL)
+        goto error;
+
+    // count the params
+    for (param = params->list; param->info.type; param++) 
+        count++;
+    
+    // initialize params
+    evsql_query_params_init_pq(&query->params, count, params->result_format);
+
+    // transform
+    for (param = params->list, idx = 0; param->info.type; param++, idx++) {
+        // `set for NULLs, otherwise not
+        query->params.types[idx] = param->bytes ? 0 : EVSQL_PQ_ARBITRARY_TYPE_OID;
+        
+        // values
+        query->params.values[idx] = param->bytes;
+
+        // lengths
+        query->params.lengths[idx] = param->length;
+
+        // formats, binary if length is nonzero, but text for NULLs
+        query->params.formats[idx] = param->length && param->bytes ? 1 : 0;
+    }
+
+    // execute it
+    if (_evsql_query_enqueue(evsql, trans, query, command))
+        goto error;
+
+#ifdef DEBUG_ENABLED
+    // debug it?
+    DEBUG("evsql.%p: enqueued query=%p on trans=%p", evsql, query, trans);
+    evsql_query_debug(command, params);
+#endif /* DEBUG_ENABLED */
+
+    // ok
+    return query;
+
+error:
+    _evsql_query_free(query);
+    
+    return NULL;
+}
+
+struct evsql_query *evsql_query_exec (struct evsql *evsql, struct evsql_trans *trans, 
+    const struct evsql_query_info *query_info,
+    evsql_query_cb query_fn, void *cb_arg,
+    ...
+) {
+    va_list vargs;
+    struct evsql_query *query = NULL;
+    const struct evsql_item_info *param;
+    size_t count = 0, idx;
+    err_t err = 1;
+
+    // varargs
+    va_start(vargs, cb_arg);
+    
+    // alloc new query
+    if ((query = _evsql_query_new(evsql, trans, query_fn, cb_arg)) == NULL)
+        goto error;
+
+    // count the params
+    for (param = query_info->params; param->type; param++) 
+        count++;
+    
+    // initialize params
+    evsql_query_params_init_pq(&query->params, count, EVSQL_FMT_BINARY);
+
+    // transform
+    for (param = params->params, idx = 0; param->info.type; param++, idx++) {
+        // default type to 0 (implicit)
+        query->params.types[idx] = 0;
+
+        // default format to binary
+        query->params.formats[idx] = EVSQL_FMT_BINARY;
+
+        // consume argument
+        switch (param->info.type) {
+            case EVSQL_TYPE_NULL_: {
+                // explicit type + text fmt
+                query->params.types[idx] = EVSQL_PQ_ARBITRARY_TYPE_OID;
+                query->params.values[idx] = NULL;
+                query->params.lengths[idx] = 0;
+                query->params.formats[idx] = EVSQL_FMT_TEXT;
+            } break;
+
+            case EVSQL_TYPE_BINARY: {
+                struct evsql_item_binary item = va_arg(vargs, struct evsql_item_binary);
+                
+                // value + explicit len
+                query->params.values[idx] = item->ptr;
+                query->params.lengths[idx] = item->len;
+            } break;
+
+            case EVSQL_TYPE_STRING: {
+                const char *str = va_arg(vargs, const char *);
+
+                // value + automatic length, text format
+                query->params.values[idx] = str;
+                query->params.lengths[idx] = 0;
+                query->params.formats[idx] = EVSQL_FMT_TEXT;
+            } break;
+            
+            case EVSQL_TYPE_UINT16: {
+                uint16_t uval = va_arg(vargs, uint16_t);
+
+                if (uval != (int16_t) uval)
+                    ERROR("param $%zu: uint16 overflow: %d", idx + 1, uval);
+                
+                // network-byte-order value + explicit len
+                query->params.values[idx] = htons(uval);
+                query->params.lengths[idx] = sizeof(uint16_t);
+            } break;
+            
+            case EVSQL_TYPE_UINT32: {
+                uint32_t uval = va_arg(vargs, uint32_t);
+
+                if (uval != (int32_t) uval)
+                    ERROR("param $%zu: uint32 overflow: %ld", idx + 1, uval);
+                
+                // network-byte-order value + explicit len
+                query->params.values[idx] = htonl(uval);
+                query->params.lengths[idx] = sizeof(uint32_t);
+            } break;
+
+            case EVSQL_TYPE_UINT64: {
+                uint64_t uval = va_arg(vargs, uint64_t);
+
+                if (uval != (int64_t) uval)
+                    ERROR("param $%zu: uint16 overflow: %lld", idx + 1, uval);
+                
+                // network-byte-order value + explicit len
+                query->params.values[idx] = htonq(uval);
+                query->params.lengths[idx] = sizeof(uint64_t);
+            } break;
+            
+            default: 
+                FATAL("param $%zu: invalid type: %d", idx + 1, param->info.type);
+        }
+    }
+
+    // execute it
+    if (_evsql_query_enqueue(evsql, trans, query, command))
+        goto error;
+    
+    // no error, fallthrough for va_end
+    err = 0;
+
+error:
+    // possible cleanup
+    if (err)
+        _evsql_query_free(query);
+    
+    // end varargs
+    va_end(vargs);
+    
+    // return 
+    return err ? NULL : query;
+}
+
+
+void evsql_query_abort (struct evsql_trans *trans, struct evsql_query *query) {
+    assert(query);
+
+    if (trans) {
+        // must be the right query
+        assert(trans->query == query);
+    }
+
+    // just strip the callback and wait for it to complete as normal
+    query->cb_fn = NULL;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/evsql/result.c	Thu Nov 20 01:16:24 2008 +0200
@@ -0,0 +1,207 @@
+
+#include "evsql.h"
+
+const char *evsql_result_error (const struct evsql_result *res) {
+    if (!res->error)
+        return "No error";
+
+    switch (res->evsql->type) {
+        case EVSQL_EVPQ:
+            if (!res->result.pq)
+                return "unknown error (no result)";
+            
+            return PQresultErrorMessage(res->result.pq);
+
+        default:
+            FATAL("res->evsql->type");
+    }
+
+}
+
+size_t evsql_result_rows (const struct evsql_result *res) {
+    switch (res->evsql->type) {
+        case EVSQL_EVPQ:
+            return PQntuples(res->result.pq);
+
+        default:
+            FATAL("res->evsql->type");
+    }
+}
+
+size_t evsql_result_cols (const struct evsql_result *res) {
+    switch (res->evsql->type) {
+        case EVSQL_EVPQ:
+            return PQnfields(res->result.pq);
+
+        default:
+            FATAL("res->evsql->type");
+    }
+}
+
+size_t evsql_result_affected (const struct evsql_result *res) {
+    switch (res->evsql->type) {
+        case EVSQL_EVPQ:
+            return strtol(PQcmdTuples(res->result.pq), NULL, 10);
+
+        default:
+            FATAL("res->evsql->type");
+    }
+}
+
+
+int evsql_result_null (const struct evsql_result *res, size_t row, size_t col) {
+    switch (res->evsql->type) {
+        case EVSQL_EVPQ:
+            return PQgetisnull(res->result.pq, row, col);
+
+        default:
+            FATAL("res->evsql->type");
+    }
+
+error:
+    return -1;
+}
+
+int evsql_result_field (const struct evsql_result *res, size_t row, size_t col, const char **ptr, size_t *size) {
+    *ptr = NULL;
+
+    switch (res->evsql->type) {
+        case EVSQL_EVPQ:
+            if (PQfformat(res->result.pq, col) != 1)
+                ERROR("[%zu:%zu] PQfformat is not binary: %d", row, col, PQfformat(res->result.pq, col));
+    
+            *size = PQgetlength(res->result.pq, row, col);
+            *ptr  = PQgetvalue(res->result.pq, row, col);
+
+            return 0;
+
+        default:
+            FATAL("res->evsql->type");
+    }
+
+error:
+    return -1;
+}
+
+err_t evsql_result_begin (struct evsql_result_info *info, struct evsql_result *res) {
+    struct evsql_item_info *col;
+    size_t cols = 0, nrows;
+    err_t err;
+
+    // count info columns
+    for (col = info->columns; col->type; col++)
+        cols++;
+
+    // number of rows returned/affected
+    nrows = evsql_result_rows(res) || evsql_result_affected(res);
+
+    // did the query fail outright?
+    if (res->error)
+        // dump error message
+        NXERROR(err = EIO, evsql_result_error(res));
+
+/*
+    // SELECT/DELETE/UPDATE WHERE didn't match any rows -> ENOENT
+    if (nrows == 0)
+        XERROR(err = ENOENT, "no rows returned/affected");
+*/
+
+    // correct number of columns
+    if (evsql_result_cols(res) != cols)
+        XERROR(err = EINVAL, "wrong number of columns: %zu -> %zu", cols, evsql_result_cols(res));
+    
+    // assign
+    res->info = info;
+    res->row_offset = 0;
+
+    // good
+    return 0;
+
+error:
+    return err;
+
+}
+
+int evsql_result_next (struct evsql_result *res, ...) {
+    va_list vargs;
+    struct evsql_item_info *col;
+    size_t col_idx, row_idx = res->row_offset;
+    int err;
+    
+    // check if we're past the end
+    if (row_idx >= evsql_result_rows(res))
+        return 0;
+    
+    // varargs
+    va_start(vargs);
+
+    for (col = info->columns, col_idx = 0; col->type; col++, col_idx++) {
+        char *value = NULL;
+        size_t length = 0;
+        
+        // check for NULLs, then try and get the field value
+        if (evsql_result_null(res, row_idx, col_idx)) {
+            if (!col->flags.null_ok)
+                XERROR(err = EINVAL, "r%zu:c%zu: NULL", row_idx, col_idx);
+
+        } else if (evsql_result_field(row_idx, col_idx, &value, &length)) {
+            EERROR(err = EINVAL);
+
+        }
+        
+        // read the arg
+        switch (col->type) {
+            case EVSQL_TYPE_BINARY: {
+                struct evsql_item_binary *item_ptr = va_arg(vargs, struct evsql_item_binary *);
+
+                if (value) {
+                    item_ptr->ptr = value;
+                    item_ptr->len = length;
+                }
+            } break;
+
+            case EVSQL_TYPE_STRING: {
+                char **str_ptr = va_arg(vargs, char **);
+
+                if (value) 
+                    *str_ptr = value;
+
+            } break;
+
+            case EVSQL_TYPE_UINT16: {
+                uint16_t *uval_ptr = va_arg(vars, uint16_t *);
+
+                if (value) {
+                    if (length != sizeof(uint16_t))
+                        XERROR(err = EINVAL, "r%zu:c%zu: wrong size for uint16_t: %zu", row_idx, col_idx, length);
+
+                    int16_t sval = ntohs(*((int16_t *) value));
+
+                    if (sval < 0)
+                        XERROR(err = ERANGE, "r%zu:c%zu: out of range for uint16_t: %d", row_idx, col_idx, sval);
+
+                    *uval_ptr = sval;
+                }
+            } break;
+        }
+    }
+
+
+}
+
+void evsql_result_end (struct evsql_result *res) {
+    // not much more to it...
+    evsql_result_free(res);
+}
+
+void evsql_result_free (struct evsql_result *res) {
+    switch (res->evsql->type) {
+        case EVSQL_EVPQ:
+            return PQclear(res->result.pq);
+
+        default:
+            FATAL("res->evsql->type");
+    }
+}
+
+
--- a/src/evsql/util.c	Wed Nov 19 19:06:10 2008 +0200
+++ b/src/evsql/util.c	Thu Nov 20 01:16:24 2008 +0200
@@ -121,54 +121,7 @@
     }
 }
 
-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) {
+int evsql_result_binary (const struct evsql_result *res, size_t row, size_t col, const char **ptr, size_t *size, int nullok) {
     *ptr = NULL;
 
     switch (res->evsql->type) {
@@ -196,7 +149,7 @@
     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) {
+int evsql_result_binlen (const struct evsql_result *res, size_t row, size_t col, const char **ptr, size_t size, int nullok) {
     size_t real_size = 0;
 
     if (evsql_result_binary(res, row, col, ptr, &real_size, nullok))
@@ -216,7 +169,7 @@
     return -1;
 }
 
-int evsql_result_string (const struct evsql_result_info *res, size_t row, size_t col, const char **ptr, int nullok) {
+int evsql_result_string (const struct evsql_result *res, size_t row, size_t col, const char **ptr, int nullok) {
     size_t real_size;
 
     if (evsql_result_binary(res, row, col, ptr, &real_size, nullok))
@@ -230,7 +183,7 @@
     return -1;
 }
 
-int evsql_result_uint16 (const struct evsql_result_info *res, size_t row, size_t col, uint16_t *uval, int nullok) {
+int evsql_result_uint16 (const struct evsql_result *res, size_t row, size_t col, uint16_t *uval, int nullok) {
     const char *data;
     int16_t sval;
 
@@ -253,7 +206,7 @@
     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) {
+int evsql_result_uint32 (const struct evsql_result *res, size_t row, size_t col, uint32_t *uval, int nullok) {
     const char *data;
     int32_t sval;
 
@@ -276,7 +229,7 @@
     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) {
+int evsql_result_uint64 (const struct evsql_result *res, size_t row, size_t col, uint64_t *uval, int nullok) {
     const char *data;
     int64_t sval;
 
@@ -299,16 +252,6 @@
     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:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/evsql_test.c	Thu Nov 20 01:16:24 2008 +0200
@@ -0,0 +1,82 @@
+
+#include <event2/event.h>
+
+#include "evsql.h"
+#include "lib/log.h"
+#include "lib/signals.h"
+#include "lib/misc.h"
+
+#define CONNINFO_DEFAULT "dbname=dbfs port=5433"
+
+void db_results (struct evsql_result *result, void *arg) {
+    uint32_t val;
+
+    static struct evsql_result_info result_info = {
+        0, {
+            {   EVSQL_FMT_BINARY,   EVSQL_TYPE_UINT32   },
+            {   0,                  0                   }
+        }
+    };
+
+
+}
+
+void do_query (struct evsql *db) {
+    struct evsql_query *query = NULL;
+
+    static struct evsql_query_info query_info = {
+        .sql    = "SELECT $1::int4 + 5",
+
+        .params = {
+            {   EVSQL_FMT_BINARY,   EVSQL_TYPE_UINT32   },
+            {   0,                  0                   }
+        }
+    };
+
+    // query
+    assert((query = evsql_query_exec(db, NULL, &query_info, (uint32_t) 4, db_results, NULL)) != NULL);
+}
+
+int main (char argc, char **argv) {
+    struct event_base *ev_base = NULL;
+    struct signals *signals = NULL;
+    struct evsql *db = NULL;
+
+    const char *db_conninfo;
+    
+    // parse 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 evsql
+    if ((db = evsql_new_pq(ev_base, db_conninfo, NULL, NULL)) == NULL)
+        ERROR("evsql_new_pq");
+
+    // run libevent
+    INFO("running libevent loop");
+
+    if (event_base_dispatch(ev_base))
+        PERROR("event_base_dispatch");
+    
+    // clean shutdown
+
+error :
+    if (db) {
+        /* evsql_close(db); */
+    }
+
+    if (signals)
+        signals_free(signals);
+
+    if (ev_base)
+        event_base_free(ev_base);
+    
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/err.h	Thu Nov 20 01:16:24 2008 +0200
@@ -0,0 +1,9 @@
+#ifndef LIB_ERR_H
+#define LIB_ERR_H
+
+/*
+ * err_t is always positive
+ */
+typedef unsigned int err_t;
+
+#endif /* LIB_ERR_H */
--- a/src/lib/error.h	Wed Nov 19 19:06:10 2008 +0200
+++ b/src/lib/error.h	Thu Nov 20 01:16:24 2008 +0200
@@ -2,11 +2,7 @@
 #define LIB_ERROR_H
 
 #include "log.h"
-
-/*
- * err_t is always positive
- */
-typedef unsigned int err_t;
+#include "err.h"
 
 #define ERROR(...) do { err_func(__func__, __VA_ARGS__); goto error; } while (0)
 #define PERROR(...) do { perr_func(__func__, __VA_ARGS__); goto error; } while (0)