# HG changeset patch # User Tero Marttila # Date 1227908771 -7200 # Node ID 424ce5ab82fd4c703e3fae4bf2332c3bca8fc1d8 # Parent 9e76ee9729b6b992989ed5d8fc6457bbe2fea8c7 compiles + some basic evsql_test diff -r 9e76ee9729b6 -r 424ce5ab82fd Makefile --- 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} diff -r 9e76ee9729b6 -r 424ce5ab82fd src/evsql.h --- 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 -#include #include -#include +#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); diff -r 9e76ee9729b6 -r 424ce5ab82fd src/evsql/evsql.c --- 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 diff -r 9e76ee9729b6 -r 424ce5ab82fd src/evsql/evsql.h --- 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 */ diff -r 9e76ee9729b6 -r 424ce5ab82fd src/evsql/query.c --- 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 +#include /* * 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 diff -r 9e76ee9729b6 -r 424ce5ab82fd src/evsql/result.c --- 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 +#include 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"); diff -r 9e76ee9729b6 -r 424ce5ab82fd src/evsql/util.c --- 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 = ¶ms->list[param]; +int evsql_param_null (struct evsql_query_params *params, size_t param) { + struct evsql_item *p = ¶ms->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 = ¶ms->list[param]; + struct evsql_item *p = ¶ms->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 = ¶ms->list[param]; + struct evsql_item *p = ¶ms->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 = ¶ms->list[param]; + struct evsql_item *p = ¶ms->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 = ¶ms->list[param]; + struct evsql_item *p = ¶ms->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(¶m->info), evsql_item_val(param)); } } diff -r 9e76ee9729b6 -r 424ce5ab82fd src/evsql_test.c --- 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 #include "evsql.h" #include "lib/log.h" #include "lib/signals.h" #include "lib/misc.h" +#include +#include + #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"); diff -r 9e76ee9729b6 -r 424ce5ab82fd src/lib/err.h --- 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 + /* * err_t is always positive */