compiles + some basic evsql_test new-evsql
authorTero Marttila <terom@fixme.fi>
Fri, 28 Nov 2008 23:46:11 +0200
branchnew-evsql
changeset 45 424ce5ab82fd
parent 44 9e76ee9729b6
child 46 75cecfc4603b
compiles + some basic evsql_test
Makefile
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
--- a/Makefile	Thu Nov 20 01:16:24 2008 +0200
+++ b/Makefile	Fri Nov 28 23:46:11 2008 +0200
@@ -24,13 +24,14 @@
 DEFINES = -D_FILE_OFFSET_BITS=64
 FIXED_CFLAGS = -Wall -std=gnu99
 
-BIN_NAMES = helloworld hello simple_hello evpq_test url_test dbfs
+BIN_NAMES = helloworld hello simple_hello evpq_test url_test dbfs evsql_test
 BIN_PATHS = $(addprefix bin/,$(BIN_NAMES))
 
 # modules
 module_objs = $(patsubst src/%.c,obj/%.o,$(wildcard src/$(1)/*.c))
 
 # complex modules
+CORE_OBJS = obj/lib/log.o obj/lib/signals.o
 EVSQL_OBJS = $(call module_objs,evsql) obj/evpq.o
 DBFS_OBJS = $(call module_objs,dbfs) obj/dirbuf.o 
 
@@ -39,11 +40,12 @@
 
 # binaries
 bin/helloworld: 
-bin/hello: obj/evfuse.o obj/dirbuf.o obj/lib/log.o obj/lib/signals.o
-bin/simple_hello: obj/evfuse.o obj/dirbuf.o obj/lib/log.o obj/lib/signals.o obj/simple.o
+bin/hello: obj/evfuse.o obj/dirbuf.o ${CORE_OBJS}
+bin/simple_hello: obj/evfuse.o obj/dirbuf.o obj/simple.o ${CORE_OBJS}
 bin/evpq_test: obj/evpq.o obj/lib/log.o
 bin/url_test: obj/lib/url.o obj/lib/lex.o obj/lib/log.o
-bin/dbfs: ${DBFS_OBJS} ${EVSQL_OBJS} obj/evfuse.o obj/lib/log.o obj/lib/signals.o
+bin/dbfs: ${DBFS_OBJS} ${EVSQL_OBJS} obj/evfuse.o ${CORE_OBJS}
+bin/evsql_test: ${EVSQL_OBJS} ${CORE_OBJS}
 
 # computed
 LDFLAGS = ${LIBRARY_PATHS}
--- a/src/evsql.h	Thu Nov 20 01:16:24 2008 +0200
+++ b/src/evsql.h	Fri Nov 28 23:46:11 2008 +0200
@@ -5,12 +5,10 @@
  * An event-based (Postgre)SQL client API using libevent
  */
 
-// XXX: remove libpq?
 #include <stdint.h>
-#include <postgresql/libpq-fe.h>
 #include <event2/event.h>
 
-#include <lib/err.h>
+#include "lib/err.h"
 
 /*
  * The generic context handle
@@ -86,24 +84,34 @@
 };
 
 /*
+ * Storage for various scalar values
+ */
+union evsql_item_value {
+    uint16_t uint16;
+    uint32_t uint32;
+    uint64_t uint64;
+};
+
+/*
  * 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
+    // pointer to the raw databytes. Set to NULL to indicate SQL-NULL, &value, or an external buf
     const char *bytes;
 
     // size of byte array pointed to by bytes, zero for text
     size_t length;
 
     // the decoded value
-    union {
-        uint16_t uint16;
-        uint32_t uint32;
-        uint64_t uint64;
-    } value;
+    union evsql_item_value value;
+    
+    // (internal) flags
+    struct {
+        char has_value : 1;
+    } flags;
 };
 
 /*
@@ -146,9 +154,9 @@
 /*
  * Callback for handling query results.
  *
- * The query has completed, either succesfully or unsuccesfully (nonzero .error).
+ * The query has completed, either succesfully or unsuccesfully.
  *
- * Use the evsql_result_* functions to manipulate the results.
+ * Use the evsql_result_* functions to manipulate the results, and call evsql_result_free (or equivalent) once done.
  */
 typedef void (*evsql_query_cb)(struct evsql_result *res, void *arg);
 
@@ -162,19 +170,20 @@
 typedef void (*evsql_error_cb)(struct evsql *evsql, void *arg);
 
 /*
- * Callback for handling transaction-level errors.
+ * Callback for handling transaction-level errors. This may be called at any time during a transaction's lifetime,
+ * including from within the evsql_query_* functions.
  *
  * The transaction is not useable anymore.
  */
 typedef void (*evsql_trans_error_cb)(struct evsql_trans *trans, void *arg);
 
 /*
- * The transaction is ready for use.
+ * Callback for handling evsql_trans/evsql_query_abort completion. The transaction is ready for use with evsql_query_*.
  */
 typedef void (*evsql_trans_ready_cb)(struct evsql_trans *trans, void *arg);
 
 /*
- * The transaction was commited, and should not be used anymore.
+ * Callback for handling evsql_trans_commit completion. The transaction was commited, and should not be used anymore.
  */
 typedef void (*evsql_trans_done_cb)(struct evsql_trans *trans, void *arg);
 
@@ -183,32 +192,52 @@
  *
  * 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);
+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.
+ * A transaction will be allocated its own connection, and the "BEGIN TRANSACTION ..." query will be sent (use the
+ * evsql_trans_type argument to specify this). 
  *
- * 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.
+ * Once the transaction has been opened, the given evsql_trans_ready_cb will be triggered, and the transaction can then
+ * be used (see evsql_query_*).
  *
+ * If, at any point, the transaction-connection fails, and pending query will be forgotten (i.e. the query callback
+ * will NOT be called), and the given evsql_trans_error_cb will be called. Note that this includes some, but not all,
+ * cases where evsql_query_* returns an error.
+ *
+ * Once you are done with the transaction, call either evsql_trans_commit or evsql_trans_abort.
  */
-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 *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.
+ * transaction's context. Otherwise, the query will be executed without a transaction using an idle connection, or
+ * enqueued for later execution.
  *
- * Once the query is complete (got a result, got an error, the connection failed), then the query_cb will be triggered.
+ * Once the query is complete (got a result, got an error, the connection failed), then the query_cb will be called.
+ * The callback can used the evsql_result_* functions to manipulate it.
+ *
+ * The returned evsql_query handle can be passed to evsql_query_abort at any point before query_fn being called. 
+ *
  */
 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.
+ * Execute the given SQL query using the list of parameter types/values given via evsql_query_params.
+ *
+ * See evsql_query for more info about behaviour.
  */
 struct evsql_query *evsql_query_params (struct evsql *evsql, struct evsql_trans *trans, 
     const char *command, const struct evsql_query_params *params, 
@@ -216,7 +245,10 @@
 );
 
 /*
- * Execute the given query_info, using the parameter list in query_info to resolve the given variable arugments
+ * Execute the given query_info's SQL query using the values given as variable arguments, using the evsql_query_info to
+ * resolve the types.
+ *
+ * See evsql_query for more info about behaviour.
  */
 struct evsql_query *evsql_query_exec (struct evsql *evsql, struct evsql_trans *trans, 
     const struct evsql_query_info *query_info,
@@ -225,30 +257,38 @@
 );
 
 /*
- * Abort a query, the query callback will not be called, the query and any possible results will be discarded.
+ * Abort a query returned by evsql_query_* that has not yet completed (query_fn has not been called yet).
  *
- * This does not garuntee that the query will not execute, simply that you won't get the results.
+ * The actual query itself may or may not be aborted (and hence may or may not be executed on the server), but query_fn
+ * will not be called anymore, and the query will dispose of itself and any results returned.
  *
  * 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.
+ * executing on that trans. The transaction's ready_fn will be called once the query has been aborted and the
+ * transaction is now idle again.
  */
 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).
+ * Commit a transaction using "COMMIT TRANSACTION".
  *
- * trans must be idle, just like for evsql_query.
- *
- * done_fn will never be called directly, always via the event loop.
+ * The transaction must be idle, just like for evsql_query. Once the transaction has been commited, the transaction's
+ * done_fn will be called, after which the transaction must not be used.
  *
  * You cannot abort a COMMIT, calling trans_abort on trans after a succesful trans_commit is a FATAL error.
+ *
+ * Note that done_fn will never be called directly, always indirectly via the event loop.
  */
 int evsql_trans_commit (struct evsql_trans *trans);
 
 /*
- * Abort a transaction, rolling it back. No callbacks will be called.
+ * Abort a transaction, using "ROLLBACK TRANSACTION".
+ *
+ * No more transaction callbacks will be called, if there was a query running, it will be aborted, and the transaction
+ * then rollback'd.
  *
  * You cannot abort a COMMIT, calling trans_abort on trans after a succesful trans_commit is a FATAL error.
+ * 
+ * Do not call evsql_trans_abort from within evsql_trans_error_cb!
  */
 void evsql_trans_abort (struct evsql_trans *trans);
 
@@ -280,18 +320,51 @@
  * Result-handling functions
  */
 
-// get error message associated with function
-const char *evsql_result_error (const struct evsql_result *res);
+/*
+ * Check the result for errors. Intended for use with non-data queries, i.e. CREATE, etc.
+ *
+ * Returns zero if the query was OK, err otherwise. EIO indicates an SQL error, the error message can be retrived
+ * using evsql_result_error.
+ */
+err_t evsql_result_check (struct evsql_result *res);
 
 /*
- * Iterator-based interface.
+ * The iterator-based interface results interface.
  *
- * Call result_begin to check for errors, then result_next to fetch rows, and finally result_end to release.
+ * Define an evsql_result_info struct that describes the columns returned by the query, and call evsql_result_begin on
+ * the evsql_result. This verifies the query result, and then prepares it for iteration using evsql_result_next.
+ *
+ * Call evsql_result_end once you've stopped iteration.
+ *
+ * Returns zero if the evsql_result is ready for iteration, err otherwise. EIO indicates an SQL error, the error
+ * message can be retreived using evsql_result_error.
+ *
+ * Note: currently the iterator state is simply stored in evsql_result, so only one iterator at a time per evsql_result.
  */
 err_t evsql_result_begin (struct evsql_result_info *info, struct evsql_result *res);
+
+/*
+ * Reads the next result row, storing the field values into the pointer arguments given. The types are resolved using
+ * the evsql_result_info given to evsql_result_begin.
+ *
+ * Returns >0 when a row was read, 0 when there are no more rows, and -err if there was an error.
+ */
 int evsql_result_next (struct evsql_result *res, ...);
+
+/*
+ * Ends the result iteration, releasing any associated resources and the result itself.
+ *
+ * The result should not be iterated or accessed anymore.
+ *
+ * Note: this does the same thing as evsql_result_free, and works regardless of evsql_result_begin returning
+ * succesfully or not.
+ */
 void evsql_result_end (struct evsql_result *res);
 
+
+// get error message associated with function
+const char *evsql_result_error (const struct evsql_result *res);
+
 // number of rows in the result
 size_t evsql_result_rows (const struct evsql_result *res);
 
--- a/src/evsql/evsql.c	Thu Nov 20 01:16:24 2008 +0200
+++ b/src/evsql/evsql.c	Fri Nov 28 23:46:11 2008 +0200
@@ -63,12 +63,7 @@
     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) {
+void _evsql_query_free (struct evsql_query *query) {
     if (!query)
         return;
         
@@ -89,13 +84,17 @@
  *
  * 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) {
+static void _evsql_query_done (struct evsql_query *query, struct evsql_result *res) {
     if (res) {
         if (query->cb_fn)
             // call the callback
             query->cb_fn(res, query->cb_arg);
-        else
+        else {
             WARNING("supressing cb_fn because query was aborted");
+            
+            // free the results
+            evsql_result_free(res);
+        }
     }
 
     // free
@@ -105,7 +104,7 @@
 /*
  * XXX:
  * /
-static void _evsql_destroy (struct evsql *evsql, const struct evsql_result_info *res) {
+static void _evsql_destroy (struct evsql *evsql, const struct evsql_result *res) {
     struct evsql_query *query;
     
     // clear the queue
@@ -184,11 +183,10 @@
  * NOTE: Only for *TRANSACTIONLESS* queries.
  */
 static void _evsql_query_fail (struct evsql* evsql, struct evsql_query *query) {
-    struct evsql_result_info res; ZINIT(res);
+    struct evsql_result res; ZINIT(res);
     
     // set up the result_info
     res.evsql = evsql;
-    res.trans = NULL;
     res.error = 1;
     
     // finish off the query
@@ -303,23 +301,23 @@
 /*
  * 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;
+static void _evsql_trans_ready (struct evsql_result *res, void *arg) {
+    struct evsql_trans *trans = arg;
 
-    assert(res->trans);
+    assert(trans != NULL);
 
     // 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);
+    trans->ready_fn(trans, trans->cb_arg);
     
     // good
     return;
 
 error:
-    _evsql_trans_fail(res->trans);
+    _evsql_trans_fail(trans);
 }
 
 /*
@@ -364,7 +362,7 @@
         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)
+    if (evsql_query(evsql, trans, trans_sql, _evsql_trans_ready, trans) == NULL)
         ERROR("evsql_query");
     
     // success
@@ -403,14 +401,14 @@
     assert(query != NULL);
 
     // if we get multiple results, only return the first one
-    if (query->result.evpq) {
+    if (query->result.pq) {
         WARNING("[evsql] evpq query returned multiple results, discarding previous one");
         
-        PQclear(query->result.evpq); query->result.evpq = NULL;
+        PQclear(query->result.pq); query->result.pq = NULL;
     }
     
     // remember the result
-    query->result.evpq = result;
+    query->result.pq = result;
 }
 
 /*
@@ -419,28 +417,27 @@
 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);
+    struct evsql_result res; ZINIT(res);
     
     assert(query != NULL);
     
     // set up the result_info
     res.evsql = conn->evsql;
-    res.trans = conn->trans;
+    res.result = query->result;
     
-    if (query->result.evpq == NULL) {
+    if (query->result.pq == NULL) {
         // if a query didn't return any results (bug?), warn and fail the query
         WARNING("[evsql] evpq query didn't return any results");
 
         res.error = 1;
     
-    } else if (strcmp(PQresultErrorMessage(query->result.evpq), "") != 0) {
+    } else if (strcmp(PQresultErrorMessage(query->result.pq), "") != 0) {
         // the query failed with some error
         res.error = 1;
-        res.result.pq = query->result.evpq;
 
     } else {
+        // the query succeeded \o/
         res.error = 0;
-        res.result.pq = query->result.evpq;
 
     }
 
@@ -733,7 +730,7 @@
 /*
  * 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 *_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
@@ -755,7 +752,7 @@
     return NULL;
 }
 
-static int _evsql_query_enqueue (struct evsql *evsql, struct evsql_trans *trans, struct evsql_query *query, const char *command) {
+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
@@ -813,26 +810,24 @@
 }
 
 
-void _evsql_trans_commit_res (const struct evsql_result_info *res, void *arg) {
-    (void) arg;
-
-    assert(res->trans);
+void _evsql_trans_commit_res (struct evsql_result *res, void *arg) {
+    struct evsql_trans *trans = arg;
 
     // check for errors
     if (res->error)
         ERROR("transaction 'COMMIT' failed: %s", evsql_result_error(res));
     
     // transaction is now done
-    res->trans->done_fn(res->trans, res->trans->cb_arg);
+    trans->done_fn(trans, trans->cb_arg);
     
     // release it
-    _evsql_trans_release(res->trans);
+    _evsql_trans_release(trans);
 
     // success
     return;
 
 error:
-    _evsql_trans_fail(res->trans);
+    _evsql_trans_fail(trans);
 }
 
 int evsql_trans_commit (struct evsql_trans *trans) {
@@ -855,37 +850,35 @@
     return -1;
 }
 
-void _evsql_trans_rollback_res (const struct evsql_result_info *res, void *arg) {
-    (void) arg;
-
-    assert(res->trans);
+void _evsql_trans_rollback_res (struct evsql_result *res, void *arg) {
+    struct evsql_trans *trans = arg;
 
     // fail the connection on errors
     if (res->error)
         ERROR("transaction 'ROLLBACK' failed: %s", evsql_result_error(res));
 
     // release it
-    _evsql_trans_release(res->trans);
+    _evsql_trans_release(trans);
 
     // success
     return;
 
 error:
     // fail the connection too, errors are supressed
-    _evsql_trans_fail(res->trans);
+    _evsql_trans_fail(trans);
 }
 
 /*
  * Used as the ready_fn callback in case of abort, otherwise directly
  */
-void _evsql_trans_rollback (struct evsql_trans *trans, void *unused) {
+void _evsql_trans_rollback (struct evsql_trans *trans, void *arg) {
     static const char *sql = "ROLLBACK TRANSACTION";
 
-    (void) unused;
+    (void) arg;
 
     // query
-    if (evsql_query(trans->evsql, trans, sql, _evsql_trans_rollback_res, NULL) == NULL) {
-        // fail the transaction/connection
+    if (evsql_query(trans->evsql, trans, sql, _evsql_trans_rollback_res, trans) == NULL) {
+        // fail the transaction/connection, errors are supressed
         _evsql_trans_fail(trans);
     }
 
@@ -904,7 +897,7 @@
         // gah, some query is running
         WARNING("aborting pending query");
         
-        // prepare to rollback once complete
+        // prepare to rollback once complete by hijacking ready_fn
         trans->ready_fn = _evsql_trans_rollback;
         
         // abort
--- a/src/evsql/evsql.h	Thu Nov 20 01:16:24 2008 +0200
+++ b/src/evsql/evsql.h	Fri Nov 28 23:46:11 2008 +0200
@@ -97,6 +97,13 @@
 };
 
 /*
+ * Backend result handle
+ */
+union evsql_result_handle {
+    PGresult *pq;
+};
+
+/*
  * A single query.
  *
  * Has the info needed to exec the query (as these may be queued), and the callback/result info.
@@ -114,12 +121,18 @@
         int *lengths;
         int *formats;
 
+        // storage for numeric values
+        union evsql_item_value *item_vals;
+
         int result_format;
     } params;
 
     // our callback
     evsql_query_cb cb_fn;
     void *cb_arg;
+        
+    // the result we get
+    union evsql_result_handle result;
 
     // our position in the query list
     TAILQ_ENTRY(evsql_query) entry;
@@ -127,11 +140,13 @@
 
 // the result
 struct evsql_result {
-    int error;
+    struct evsql *evsql;
 
-    union {
-        PGresult *pq;
-    } result;
+    // possible error code
+    int error;
+    
+    // the actual result
+    union evsql_result_handle result;
 
     // result_* state
     struct evsql_result_info *info;
@@ -155,7 +170,7 @@
  *
  * 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);
+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.
@@ -165,11 +180,13 @@
  *
  * 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);
+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.
+ * Free the query and related resources, doesn't trigger any callbacks or remove from any queues.
+ *
+ * The command should already be taken care of (NULL).
  */
-
+void _evsql_query_free (struct evsql_query *query);
 
 #endif /* EVSQL_INTERNAL_H */
--- a/src/evsql/query.c	Thu Nov 20 01:16:24 2008 +0200
+++ b/src/evsql/query.c	Fri Nov 28 23:46:11 2008 +0200
@@ -1,5 +1,10 @@
 
 #include "evsql.h"
+#include "../lib/error.h"
+#include "../lib/misc.h"
+
+#include <stdlib.h>
+#include <assert.h>
 
 /*
  * Initialize params->types/values/lengths/formats, params->count, params->result_format based on the given args
@@ -11,20 +16,21 @@
     // 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)))
+        ||  !(params->types     = calloc(param_count, sizeof(Oid)))
+        ||  !(params->values    = calloc(param_count, sizeof(char *)))
+        ||  !(params->lengths   = calloc(param_count, sizeof(int)))
+        ||  !(params->formats   = calloc(param_count, sizeof(int)))
+        ||  !(params->item_vals = calloc(param_count, sizeof(union evsql_item_value)))
     )
         ERROR("calloc");
 
     // result format
     switch (result_format) {
         case EVSQL_FMT_TEXT:
-            params.result_format = 0; break;
+            params->result_format = 0; break;
 
         case EVSQL_FMT_BINARY:
-            params.result_format = 1; break;
+            params->result_format = 1; break;
 
         default:
             FATAL("params.result_fmt: %d", result_format);
@@ -74,15 +80,19 @@
         count++;
     
     // initialize params
-    evsql_query_params_init_pq(&query->params, count, params->result_format);
+    _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;
+
+        // scalar values
+        query->params.item_vals[idx] = param->value;
         
         // values
-        query->params.values[idx] = param->bytes;
+        // point this at the value stored in the item_vals union if flagged as such
+        query->params.values[idx] = param->flags.has_value ? (const char *) &query->params.item_vals[idx] : param->bytes;
 
         // lengths
         query->params.lengths[idx] = param->length;
@@ -133,10 +143,10 @@
         count++;
     
     // initialize params
-    evsql_query_params_init_pq(&query->params, count, EVSQL_FMT_BINARY);
+    _evsql_query_params_init_pq(&query->params, count, EVSQL_FMT_BINARY);
 
     // transform
-    for (param = params->params, idx = 0; param->info.type; param++, idx++) {
+    for (param = query_info->params, idx = 0; param->type; param++, idx++) {
         // default type to 0 (implicit)
         query->params.types[idx] = 0;
 
@@ -144,7 +154,7 @@
         query->params.formats[idx] = EVSQL_FMT_BINARY;
 
         // consume argument
-        switch (param->info.type) {
+        switch (param->type) {
             case EVSQL_TYPE_NULL_: {
                 // explicit type + text fmt
                 query->params.types[idx] = EVSQL_PQ_ARBITRARY_TYPE_OID;
@@ -157,8 +167,8 @@
                 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;
+                query->params.values[idx] = item.ptr;
+                query->params.lengths[idx] = item.len;
             } break;
 
             case EVSQL_TYPE_STRING: {
@@ -171,13 +181,15 @@
             } break;
             
             case EVSQL_TYPE_UINT16: {
-                uint16_t uval = va_arg(vargs, uint16_t);
+                // XXX: uint16_t is passed as `int'?
+                uint16_t uval = va_arg(vargs, int);
 
                 if (uval != (int16_t) uval)
                     ERROR("param $%zu: uint16 overflow: %d", idx + 1, uval);
                 
                 // network-byte-order value + explicit len
-                query->params.values[idx] = htons(uval);
+                query->params.item_vals[idx].uint16 = htons(uval);
+                query->params.values[idx] = (const char *) &query->params.item_vals[idx];
                 query->params.lengths[idx] = sizeof(uint16_t);
             } break;
             
@@ -185,10 +197,11 @@
                 uint32_t uval = va_arg(vargs, uint32_t);
 
                 if (uval != (int32_t) uval)
-                    ERROR("param $%zu: uint32 overflow: %ld", idx + 1, uval);
+                    ERROR("param $%zu: uint32 overflow: %ld", idx + 1, (unsigned long) uval);
                 
                 // network-byte-order value + explicit len
-                query->params.values[idx] = htonl(uval);
+                query->params.item_vals[idx].uint32 = htonl(uval);
+                query->params.values[idx] = (const char *) &query->params.item_vals[idx];
                 query->params.lengths[idx] = sizeof(uint32_t);
             } break;
 
@@ -196,20 +209,21 @@
                 uint64_t uval = va_arg(vargs, uint64_t);
 
                 if (uval != (int64_t) uval)
-                    ERROR("param $%zu: uint16 overflow: %lld", idx + 1, uval);
+                    ERROR("param $%zu: uint16 overflow: %lld", idx + 1, (unsigned long long) uval);
                 
                 // network-byte-order value + explicit len
-                query->params.values[idx] = htonq(uval);
+                query->params.item_vals[idx].uint64 = htonq(uval);
+                query->params.values[idx] = (const char *) &query->params.item_vals[idx];
                 query->params.lengths[idx] = sizeof(uint64_t);
             } break;
             
             default: 
-                FATAL("param $%zu: invalid type: %d", idx + 1, param->info.type);
+                FATAL("param $%zu: invalid type: %d", idx + 1, param->type);
         }
     }
 
     // execute it
-    if (_evsql_query_enqueue(evsql, trans, query, command))
+    if (_evsql_query_enqueue(evsql, trans, query, query_info->sql))
         goto error;
     
     // no error, fallthrough for va_end
--- a/src/evsql/result.c	Thu Nov 20 01:16:24 2008 +0200
+++ b/src/evsql/result.c	Fri Nov 28 23:46:11 2008 +0200
@@ -1,5 +1,10 @@
 
 #include "evsql.h"
+#include "../lib/error.h"
+#include "../lib/misc.h"
+
+#include <stdlib.h>
+#include <assert.h>
 
 const char *evsql_result_error (const struct evsql_result *res) {
     if (!res->error)
@@ -41,6 +46,7 @@
 size_t evsql_result_affected (const struct evsql_result *res) {
     switch (res->evsql->type) {
         case EVSQL_EVPQ:
+            // XXX: errors?
             return strtol(PQcmdTuples(res->result.pq), NULL, 10);
 
         default:
@@ -57,12 +63,9 @@
         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) {
+int evsql_result_field (const struct evsql_result *res, size_t row, size_t col, char ** const ptr, size_t *size) {
     *ptr = NULL;
 
     switch (res->evsql->type) {
@@ -83,6 +86,11 @@
     return -1;
 }
 
+err_t evsql_result_check (struct evsql_result *res) {
+    // so simple...
+    return res->error ? EIO : 0;
+}
+
 err_t evsql_result_begin (struct evsql_result_info *info, struct evsql_result *res) {
     struct evsql_item_info *col;
     size_t cols = 0, nrows;
@@ -126,16 +134,19 @@
     va_list vargs;
     struct evsql_item_info *col;
     size_t col_idx, row_idx = res->row_offset;
-    int err;
+    err_t err;
+    
+    // ensure that evsql_result_begin has been called
+    assert(res->info);
     
     // check if we're past the end
     if (row_idx >= evsql_result_rows(res))
         return 0;
     
     // varargs
-    va_start(vargs);
+    va_start(vargs, res);
 
-    for (col = info->columns, col_idx = 0; col->type; col++, col_idx++) {
+    for (col = res->info->columns, col_idx = 0; col->type; col++, col_idx++) {
         char *value = NULL;
         size_t length = 0;
         
@@ -144,8 +155,8 @@
             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);
+        } else if (evsql_result_field(res, row_idx, col_idx, &value, &length)) {
+            SERROR(err = EINVAL);
 
         }
         
@@ -163,30 +174,64 @@
             case EVSQL_TYPE_STRING: {
                 char **str_ptr = va_arg(vargs, char **);
 
-                if (value) 
+                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);
+                uint16_t *uval_ptr = va_arg(vargs, uint16_t *);
 
-                    int16_t sval = ntohs(*((int16_t *) value));
+                if (!value) break;
 
-                    if (sval < 0)
-                        XERROR(err = ERANGE, "r%zu:c%zu: out of range for uint16_t: %d", row_idx, col_idx, sval);
+                if (length != sizeof(uint16_t)) XERROR(err = EINVAL, "r%zu:c%zu: wrong size for uint16_t: %zu", row_idx, col_idx, length);
 
-                    *uval_ptr = sval;
-                }
+                int16_t sval = ntohs(*((int16_t *) value));
+
+                if (sval < 0) XERROR(err = ERANGE, "r%zu:c%zu: out of range for uint16_t: %hd", row_idx, col_idx, (signed short) sval);
+
+                *uval_ptr = sval;
             } break;
+            
+            case EVSQL_TYPE_UINT32: {
+                uint32_t *uval_ptr = va_arg(vargs, uint32_t *);
+
+                if (!value) break;
+
+                if (length != sizeof(uint32_t)) XERROR(err = EINVAL, "r%zu:c%zu: wrong size for uint32_t: %zu", row_idx, col_idx, length);
+
+                int32_t sval = ntohl(*((int32_t *) value));
+
+                if (sval < 0) XERROR(err = ERANGE, "r%zu:c%zu: out of range for uint32_t: %ld", row_idx, col_idx, (signed long) sval);
+
+                *uval_ptr = sval;
+            } break;
+            
+            case EVSQL_TYPE_UINT64: {
+                uint64_t *uval_ptr = va_arg(vargs, uint64_t *);
+
+                if (!value) break;
+
+                if (length != sizeof(uint64_t)) XERROR(err = EINVAL, "r%zu:c%zu: wrong size for uint64_t: %zu", row_idx, col_idx, length);
+
+                int64_t sval = ntohq(*((int64_t *) value));
+
+                if (sval < 0) XERROR(err = ERANGE, "r%zu:c%zu: out of range for uint64_t: %lld", row_idx, col_idx, (signed long long) sval);
+
+                *uval_ptr = sval;
+            } break;
+            
+            default:
+                XERROR(err = EINVAL, "r%zu:c%zu: invalid type: %d", row_idx, col_idx, col->type);
         }
     }
 
+    // row handled succesfully
+    return 1;
 
+error:
+    return -err;
 }
 
 void evsql_result_end (struct evsql_result *res) {
@@ -195,9 +240,12 @@
 }
 
 void evsql_result_free (struct evsql_result *res) {
+    // note that the result itself might be NULL...
+    // in the case of internal-error results, these may be free'd multiple times!
     switch (res->evsql->type) {
         case EVSQL_EVPQ:
-            return PQclear(res->result.pq);
+            if (res->result.pq)
+                return PQclear(res->result.pq);
 
         default:
             FATAL("res->evsql->type");
--- a/src/evsql/util.c	Thu Nov 20 01:16:24 2008 +0200
+++ b/src/evsql/util.c	Fri Nov 28 23:46:11 2008 +0200
@@ -5,13 +5,13 @@
 #include "../lib/log.h"
 #include "../lib/misc.h"
 
-#define _PARAM_TYPE_CASE(typenam) case EVSQL_PARAM_ ## typenam: return #typenam
+#define _PARAM_TYPE_CASE(typenam) case EVSQL_TYPE_ ## 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
+#define _PARAM_VAL_CASE(typenam, ...) case EVSQL_TYPE_ ## typenam: if (item->bytes) ret = snprintf(buf, _PARAM_VAL_BUF_MAX, __VA_ARGS__); else return "(null)"; break
 
-const char *evsql_param_type (const struct evsql_query_param *param) {
-    switch (param->type) {
+const char *evsql_item_type (const struct evsql_item_info *item_info) {
+    switch (item_info->type) {
         _PARAM_TYPE_CASE (INVALID   );
         _PARAM_TYPE_CASE (NULL_     );
         _PARAM_TYPE_CASE (BINARY    );
@@ -24,18 +24,18 @@
 }
 
 
-static const char *evsql_param_val (const struct evsql_query_param *param) {
+static const char *evsql_item_val (const struct evsql_item *item) {
     static char buf[_PARAM_VAL_BUF_MAX];
     int ret;
 
-    switch (param->type) {
+    switch (item->info.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)  );
+        _PARAM_VAL_CASE (BINARY,    "%zu:%s",   item->length, "... "    );
+        _PARAM_VAL_CASE (STRING,    "%s",       item->bytes             );
+        _PARAM_VAL_CASE (UINT16,    "%hu",      (unsigned short int)     ntohs(item->value.uint16)  );
+        _PARAM_VAL_CASE (UINT32,    "%lu",      (unsigned long int)      ntohl(item->value.uint32)  );
+        _PARAM_VAL_CASE (UINT64,    "%llu",     (unsigned long long int) ntohq(item->value.uint64)  );
         default: return "???";
     }
 
@@ -43,81 +43,82 @@
 }
 
 int evsql_params_clear (struct evsql_query_params *params) {
-    struct evsql_query_param *param;
+    struct evsql_item *param;
 
-    for (param = params->list; param->type; param++) 
-        param->data_raw = NULL;
+    for (param = params->list; param->info.type; param++) 
+        param->bytes = NULL;
 
     return 0;
 }
 
-int evsql_param_null   (struct evsql_query_params *params, size_t param) {
-    struct evsql_query_param *p = &params->list[param];
+int evsql_param_null (struct evsql_query_params *params, size_t param) {
+    struct evsql_item *p = &params->list[param];
 
-    p->data_raw = NULL;
+    p->bytes = NULL;
+    p->flags.has_value = 0;
 
     return 0;
 }
 
 int evsql_param_binary (struct evsql_query_params *params, size_t param, const char *ptr, size_t len) {
-    struct evsql_query_param *p = &params->list[param];
+    struct evsql_item *p = &params->list[param];
     
-    assert(p->type == EVSQL_PARAM_BINARY);
+    assert(p->info.type == EVSQL_TYPE_BINARY);
 
-    p->data_raw = ptr;
+    p->bytes = ptr;
     p->length = len;
 
     return 0;
 }
 
 int evsql_param_string (struct evsql_query_params *params, size_t param, const char *ptr) {
-    struct evsql_query_param *p = &params->list[param];
+    struct evsql_item *p = &params->list[param];
     
-    assert(p->type == EVSQL_PARAM_STRING);
+    assert(p->info.type == EVSQL_TYPE_STRING);
 
-    p->data_raw = ptr;
+    p->bytes = ptr;
     p->length = 0;
 
     return 0;
 }
 
 int evsql_param_uint16 (struct evsql_query_params *params, size_t param, uint16_t uval) {
-    struct evsql_query_param *p = &params->list[param];
+    struct evsql_item *p = &params->list[param];
     
-    assert(p->type == EVSQL_PARAM_UINT16);
+    assert(p->info.type == EVSQL_TYPE_UINT16);
 
-    p->data.uint16 = htons(uval);
-    p->data_raw = (const char *) &p->data.uint16;
+    p->value.uint16 = htons(uval);
     p->length = sizeof(uval);
+    p->flags.has_value = 1;
 
     return 0;
 }
 
 int evsql_param_uint32 (struct evsql_query_params *params, size_t param, uint32_t uval) {
-    struct evsql_query_param *p = &params->list[param];
+    struct evsql_item *p = &params->list[param];
     
-    assert(p->type == EVSQL_PARAM_UINT32);
+    assert(p->info.type == EVSQL_TYPE_UINT32);
 
-    p->data.uint32 = htonl(uval);
-    p->data_raw = (const char *) &p->data.uint32;
+    p->value.uint32 = htonl(uval);
     p->length = sizeof(uval);
+    p->flags.has_value = 1;
 
     return 0;
 }
 
 void evsql_query_debug (const char *sql, const struct evsql_query_params *params) {
-    const struct evsql_query_param *param;
+    const struct evsql_item *param;
     size_t param_count = 0, idx = 0;
 
     // count the params
-    for (param = params->list; param->type; param++) 
+    for (param = params->list; param->info.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));
+    for (param = params->list; param->info.type; param++) {
+        DEBUG("\t%2zu : %8s = %s", ++idx, evsql_item_type(&param->info), evsql_item_val(param));
     }
 }
 
--- a/src/evsql_test.c	Thu Nov 20 01:16:24 2008 +0200
+++ b/src/evsql_test.c	Fri Nov 28 23:46:11 2008 +0200
@@ -1,14 +1,15 @@
-
-#include <event2/event.h>
 
 #include "evsql.h"
 #include "lib/log.h"
 #include "lib/signals.h"
 #include "lib/misc.h"
 
+#include <event2/event.h>
+#include <assert.h>
+
 #define CONNINFO_DEFAULT "dbname=dbfs port=5433"
 
-void db_results (struct evsql_result *result, void *arg) {
+void query_results (struct evsql_result *result, void *arg) {
     uint32_t val;
 
     static struct evsql_result_info result_info = {
@@ -18,11 +19,24 @@
         }
     };
 
+    // begin
+    assert(evsql_result_begin(&result_info, result) == 0);
 
+    // one row
+    assert(evsql_result_next(result, 
+        &val
+    ) > 0);
+
+    // print
+    INFO("[evsql_test.results] got result: %p: val=%lu", result, (unsigned long) val);
+
+    // done
+    evsql_result_end(result);
 }
 
-void do_query (struct evsql *db) {
+void query_send (struct evsql *db, struct evsql_trans *trans) {
     struct evsql_query *query = NULL;
+    static int query_id = 0;
 
     static struct evsql_query_info query_info = {
         .sql    = "SELECT $1::int4 + 5",
@@ -34,10 +48,77 @@
     };
 
     // query
-    assert((query = evsql_query_exec(db, NULL, &query_info, (uint32_t) 4, db_results, NULL)) != NULL);
+    assert((query = evsql_query_exec(db, trans, &query_info, query_results, NULL,
+        (uint32_t) ++query_id
+    )) != NULL);
+
+    INFO("[evsql_test.query_send] enqueued query, trans=%p: %p: %d", trans, query, query_id);
 }
 
-int main (char argc, char **argv) {
+void trans_create_result (struct evsql_result *res, void *arg) {
+    // check
+    if (evsql_result_check(res))
+        FATAL("query failed: %s", evsql_result_error(res));
+    
+    INFO("[evsql_test.create_result] table created succesfully: %p", res);
+
+    // free
+    evsql_result_free(res);
+}
+
+void trans_create_query (struct evsql *db, struct evsql_trans *trans) {
+    struct evsql_query *query = NULL;
+
+    // the query info
+    static struct evsql_query_info query_info = {
+        .sql    = "CREATE TEMPORARY TABLE evsql_test ( id serial4, str varchar(32) DEFAULT $1::varchar ) ON COMMIT DROP",
+
+        .params = {
+            {   EVSQL_FMT_BINARY,   EVSQL_TYPE_STRING   },
+            {   0,                  0,                  }
+        }
+    };
+
+    // run the query
+    assert((query = evsql_query_exec(db, trans, &query_info, trans_create_result, NULL,
+        (const char *) "foobar"
+    )) != NULL);
+
+    INFO("[evsql_test.trans_create_query] enqueued query: %p", query);
+}
+
+void trans_error (struct evsql_trans *trans, void *arg) {
+    struct evsql *db = arg;
+
+    FATAL("[evsql_test.trans_error] failure: %s", evsql_trans_error(trans));
+}
+
+void trans_ready (struct evsql_trans *trans, void *arg) {
+    struct evsql *db = arg;
+
+    INFO("[evsql_test.trans_ready] ready");
+    
+    trans_create_query(db, trans);
+}
+
+void trans_done (struct evsql_trans *trans, void *arg) {
+    struct evsql *db = arg;
+
+    INFO("[evsql_test.trans_done] done");
+}
+
+void begin_transaction (struct evsql *db) {
+    struct evsql_trans *trans;
+
+    assert((trans = evsql_trans(db, EVSQL_TRANS_DEFAULT, 
+        &trans_error, &trans_ready, &trans_done,
+        db
+    )) != NULL);
+
+    INFO("[evsql_test.begin_trans] created transaction");
+ }
+
+int main (int argc, char **argv) {
     struct event_base *ev_base = NULL;
     struct signals *signals = NULL;
     struct evsql *db = NULL;
@@ -59,6 +140,15 @@
     if ((db = evsql_new_pq(ev_base, db_conninfo, NULL, NULL)) == NULL)
         ERROR("evsql_new_pq");
 
+    // send query
+    query_send(db, NULL);
+
+    // being transaction
+    begin_transaction(db);
+
+    // send query
+    query_send(db, NULL);
+
     // run libevent
     INFO("running libevent loop");
 
--- a/src/lib/err.h	Thu Nov 20 01:16:24 2008 +0200
+++ b/src/lib/err.h	Fri Nov 28 23:46:11 2008 +0200
@@ -1,6 +1,8 @@
 #ifndef LIB_ERR_H
 #define LIB_ERR_H
 
+#include <errno.h>
+
 /*
  * err_t is always positive
  */