terom@21: #define _GNU_SOURCE terom@21: #include terom@21: #include terom@21: #include terom@21: terom@21: #include "evsql.h" terom@25: #include "evsql_internal.h" terom@21: #include "evpq.h" terom@21: #include "lib/log.h" terom@21: #include "lib/error.h" terom@21: #include "lib/misc.h" terom@21: terom@23: terom@21: /* terom@21: * Actually execute the given query. terom@21: * terom@21: * The backend should be able to accept the query at this time. terom@21: * terom@25: * You should assume that if trying to execute a query fails, then the connection should also be considred as failed. terom@21: */ terom@25: static int _evsql_query_exec (struct evsql_conn *conn, struct evsql_query *query, const char *command) { terom@25: int err; terom@25: terom@25: switch (conn->evsql->type) { terom@21: case EVSQL_EVPQ: terom@23: // got params? terom@23: if (query->params.count) { terom@25: err = evpq_query_params(conn->engine.evpq, command, terom@23: query->params.count, terom@23: query->params.types, terom@23: query->params.values, terom@23: query->params.lengths, terom@23: query->params.formats, terom@23: query->params.result_format terom@23: ); terom@23: terom@23: } else { terom@23: // plain 'ole query terom@25: err = evpq_query(conn->engine.evpq, command); terom@23: } terom@25: terom@25: if (err) { terom@25: if (PQstatus(evpq_pgconn(conn->engine.evpq)) != CONNECTION_OK) terom@25: WARNING("conn failed"); terom@25: else terom@25: WARNING("query failed, dropping conn as well"); terom@25: } terom@25: terom@25: break; terom@21: terom@21: default: terom@21: FATAL("evsql->type"); terom@21: } terom@25: terom@25: if (!err) terom@25: // assign the query terom@25: conn->query = query; terom@25: terom@25: return err; terom@21: } terom@21: terom@21: /* terom@25: * Free the query and related resources, doesn't trigger any callbacks or remove from any queues. terom@25: * terom@25: * The command should already be taken care of (NULL). terom@23: */ terom@23: static void _evsql_query_free (struct evsql_query *query) { terom@23: assert(query->command == NULL); terom@23: terom@23: // free params if present terom@23: free(query->params.types); terom@23: free(query->params.values); terom@23: free(query->params.lengths); terom@23: free(query->params.formats); terom@23: terom@23: // free the query itself terom@23: free(query); terom@23: } terom@23: terom@23: /* terom@25: * Execute the callback if res is given, and free the query. terom@21: */ terom@24: static void _evsql_query_done (struct evsql_query *query, const struct evsql_result_info *res) { terom@24: if (res) terom@21: // call the callback terom@24: query->cb_fn(res, query->cb_arg); terom@21: terom@21: // free terom@23: _evsql_query_free(query); terom@21: } terom@21: terom@21: /* terom@25: * XXX: terom@25: * / terom@24: static void _evsql_destroy (struct evsql *evsql, const struct evsql_result_info *res) { terom@21: struct evsql_query *query; terom@21: terom@21: // clear the queue terom@25: while ((query = TAILQ_FIRST(&evsql->query_queue)) != NULL) { terom@24: _evsql_query_done(query, res); terom@21: terom@25: TAILQ_REMOVE(&evsql->query_queue, query, entry); terom@21: } terom@21: terom@21: // free terom@21: free(evsql); terom@21: } terom@25: */ terom@21: terom@21: /* terom@25: * Free the transaction, it should already be deassociated from the query and conn. terom@21: */ terom@25: static void _evsql_trans_free (struct evsql_trans *trans) { terom@25: // ensure we don't leak anything terom@25: assert(trans->query == NULL); terom@25: assert(trans->conn == NULL); terom@21: terom@25: // free terom@25: free(trans); terom@25: } terom@25: terom@25: /* terom@25: * Release a connection. It should already be deassociated from the trans and query. terom@25: * terom@25: * Releases the engine, removes from the conn_list and frees this. terom@25: */ terom@25: static void _evsql_conn_release (struct evsql_conn *conn) { terom@25: // ensure we don't leak anything terom@25: assert(conn->trans == NULL); terom@25: assert(conn->query == NULL); terom@25: terom@25: // release the engine terom@25: switch (conn->evsql->type) { terom@25: case EVSQL_EVPQ: terom@25: evpq_release(conn->engine.evpq); terom@25: break; terom@25: terom@25: default: terom@25: FATAL("evsql->type"); terom@25: } terom@25: terom@25: // remove from list terom@25: LIST_REMOVE(conn, entry); terom@25: terom@25: // free terom@25: free(conn); terom@25: } terom@25: terom@25: /* terom@25: * Fail a single query, this will trigger the callback and free it. terom@25: */ terom@25: static void _evsql_query_fail (struct evsql* evsql, struct evsql_query *query) { terom@25: struct evsql_result_info res; ZINIT(res); terom@25: terom@25: // set up the result_info terom@25: res.evsql = evsql; terom@25: res.error = 1; terom@25: terom@25: // finish off the query terom@25: _evsql_query_done(query, &res); terom@25: } terom@25: terom@25: /* terom@25: * Fail a transaction, this will silently drop any query, trigger the error callback, two-way-deassociate/release the terom@25: * conn, and then free the trans. terom@25: */ terom@25: static void _evsql_trans_fail (struct evsql_trans *trans) { terom@25: if (trans->query) { terom@25: // free the query silently terom@25: _evsql_query_free(trans->query); trans->query = NULL; terom@25: } terom@25: terom@25: // tell the user terom@25: // XXX: trans is in a bad state during this call terom@25: trans->error_fn(trans, trans->cb_arg); terom@25: terom@25: // fail the conn terom@25: trans->conn->trans = NULL; _evsql_conn_release(trans->conn); trans->conn = NULL; terom@25: terom@25: // free the trans terom@25: _evsql_trans_free(trans); terom@25: } terom@25: terom@25: /* terom@25: * Fail a connection. If the connection is transactional, this will just call _evsql_trans_fail, but otherwise it will terom@25: * fail any ongoing query, and then release the connection. terom@25: */ terom@25: static void _evsql_conn_fail (struct evsql_conn *conn) { terom@25: if (conn->trans) { terom@25: // let transactions handle their connection failures terom@25: _evsql_trans_fail(conn->trans); terom@25: terom@25: } else { terom@25: if (conn->query) { terom@25: // fail the in-progress query terom@25: _evsql_query_fail(conn->evsql, conn->query); conn->query = NULL; terom@21: } terom@21: terom@25: // finish off the whole connection terom@25: _evsql_conn_release(conn); terom@21: } terom@21: } terom@21: terom@25: /* terom@25: * Processes enqueued non-transactional queries until the queue is empty, or we managed to exec a query. terom@25: * terom@25: * If execing a query on a connection fails, both the query and the connection are failed (in that order). terom@25: * terom@25: * Any further queries will then also be failed, because there's no reconnection/retry logic yet. terom@25: */ terom@25: static void _evsql_pump (struct evsql *evsql, struct evsql_conn *conn) { terom@25: struct evsql_query *query; terom@25: int err; terom@25: terom@25: // look for waiting queries terom@25: while ((query = TAILQ_FIRST(&evsql->query_queue)) != NULL) { terom@25: // dequeue terom@25: TAILQ_REMOVE(&evsql->query_queue, query, entry); terom@25: terom@25: if (conn) { terom@25: // try and execute it terom@25: err = _evsql_query_exec(conn, query, query->command); terom@25: } terom@21: terom@25: // free the command buf terom@25: free(query->command); query->command = NULL; terom@21: terom@25: if (err || !conn) { terom@25: if (!conn) { terom@25: // warn when dropping queries terom@25: WARNING("failing query becuse there are no conns"); terom@25: } terom@25: terom@25: // fail the query terom@25: _evsql_query_fail(evsql, query); terom@25: terom@25: if (conn) { terom@25: // fail the connection terom@25: WARNING("failing the connection because a query-exec failed"); terom@25: terom@25: _evsql_conn_fail(conn); conn = NULL; terom@25: } terom@25: terom@25: } else { terom@25: // we have succesfully enqueued a query, and we can wait for this connection to complete terom@25: break; terom@25: terom@25: } terom@25: terom@25: // handle the rest of the queue terom@25: } terom@25: terom@25: // ok terom@25: return; terom@21: } terom@21: terom@25: /* terom@25: * Callback for a trans's 'BEGIN' query, which means the transaction is now ready for use. terom@25: */ terom@25: static void _evsql_trans_ready (const struct evsql_result_info *res, void *arg) { terom@25: (void) arg; terom@21: terom@25: assert(res->trans); terom@25: terom@25: // check for errors terom@25: if (res->error) terom@25: ERROR("transaction 'BEGIN' failed: %s", evsql_result_error(res)); terom@25: terom@25: // transaction is now ready for use terom@25: res->trans->ready_fn(res->trans, res->trans->cb_arg); terom@25: terom@25: error: terom@25: _evsql_trans_fail(res->trans); terom@25: } terom@25: terom@25: /* terom@25: * The transaction's connection is ready, send the 'BEGIN' query. terom@25: */ terom@25: static void _evsql_trans_conn_ready (struct evsql *evsql, struct evsql_trans *trans) { terom@25: char trans_sql[EVSQL_QUERY_BEGIN_BUF]; terom@25: const char *isolation_level; terom@25: int ret; terom@25: terom@25: // determine the isolation_level to use terom@25: switch (trans->type) { terom@25: case EVSQL_TRANS_DEFAULT: terom@25: isolation_level = NULL; break; terom@25: terom@25: case EVSQL_TRANS_SERIALIZABLE: terom@25: isolation_level = "SERIALIZABLE"; break; terom@25: terom@25: case EVSQL_TRANS_REPEATABLE_READ: terom@25: isolation_level = "REPEATABLE READ"; break; terom@25: terom@25: case EVSQL_TRANS_READ_COMMITTED: terom@25: isolation_level = "READ COMMITTED"; break; terom@25: terom@25: case EVSQL_TRANS_READ_UNCOMMITTED: terom@25: isolation_level = "READ UNCOMMITTED"; break; terom@25: terom@25: default: terom@25: FATAL("trans->type: %d", trans->type); terom@25: } terom@25: terom@25: // build the trans_sql terom@25: if (isolation_level) terom@25: ret = snprintf(trans_sql, EVSQL_QUERY_BEGIN_BUF, "BEGIN TRANSACTION ISOLATION LEVEL %s", isolation_level); terom@25: else terom@25: ret = snprintf(trans_sql, EVSQL_QUERY_BEGIN_BUF, "BEGIN TRANSACTION"); terom@25: terom@25: // make sure it wasn't truncated terom@25: if (ret >= EVSQL_QUERY_BEGIN_BUF) terom@25: ERROR("trans_sql overflow: %d >= %d", ret, EVSQL_QUERY_BEGIN_BUF); terom@25: terom@25: // execute the query terom@25: if (evsql_query(evsql, trans, trans_sql, _evsql_trans_ready, NULL)) terom@25: ERROR("evsql_query"); terom@25: terom@25: // success terom@25: return; terom@25: terom@25: error: terom@25: // fail the transaction terom@25: _evsql_trans_fail(trans); terom@25: } terom@25: terom@25: /* terom@25: * The evpq connection was succesfully established. terom@25: */ terom@25: static void _evsql_evpq_connected (struct evpq_conn *_conn, void *arg) { terom@25: struct evsql_conn *conn = arg; terom@25: terom@25: if (conn->trans) terom@25: // notify the transaction terom@25: _evsql_trans_conn_ready(conn->evsql, conn->trans); terom@25: terom@25: else terom@25: // pump any waiting transactionless queries terom@25: _evsql_pump(conn->evsql, conn); terom@25: } terom@25: terom@25: /* terom@25: * Got one result on this evpq connection. terom@25: */ terom@25: static void _evsql_evpq_result (struct evpq_conn *_conn, PGresult *result, void *arg) { terom@25: struct evsql_conn *conn = arg; terom@25: struct evsql_query *query = conn->query; terom@25: terom@25: assert(query != NULL); terom@21: terom@21: // if we get multiple results, only return the first one terom@21: if (query->result.evpq) { terom@21: WARNING("[evsql] evpq query returned multiple results, discarding previous one"); terom@21: terom@21: PQclear(query->result.evpq); query->result.evpq = NULL; terom@21: } terom@21: terom@21: // remember the result terom@21: query->result.evpq = result; terom@21: } terom@21: terom@25: /* terom@25: * No more results for this query. terom@25: */ terom@25: static void _evsql_evpq_done (struct evpq_conn *_conn, void *arg) { terom@25: struct evsql_conn *conn = arg; terom@25: struct evsql_query *query = conn->query; terom@24: struct evsql_result_info res; ZINIT(res); terom@25: terom@25: assert(query != NULL); terom@21: terom@21: // set up the result_info terom@25: res.evsql = conn->evsql; terom@21: terom@21: if (query->result.evpq == NULL) { terom@21: // if a query didn't return any results (bug?), warn and fail the query terom@21: WARNING("[evsql] evpq query didn't return any results"); terom@21: terom@24: res.error = 1; terom@24: terom@24: } else if (strcmp(PQresultErrorMessage(query->result.evpq), "") != 0) { terom@24: // the query failed with some error terom@24: res.error = 1; terom@24: res.result.pq = query->result.evpq; terom@21: terom@21: } else { terom@24: res.error = 0; terom@24: res.result.pq = query->result.evpq; terom@21: terom@21: } terom@21: terom@25: // de-associate the query from the connection terom@25: conn->query = NULL; terom@25: terom@25: // how we handle query completion depends on if we're a transaction or not terom@25: if (conn->trans) { terom@25: // we can deassign the trans's query terom@25: conn->trans->query = NULL; terom@21: terom@25: // then hand the query to the user terom@25: _evsql_query_done(query, &res); terom@25: terom@25: } else { terom@25: // a transactionless query, so just finish it off and pump any other waiting ones terom@25: _evsql_query_done(query, &res); terom@25: terom@25: // pump the next one terom@25: _evsql_pump(conn->evsql, conn); terom@25: } terom@21: } terom@21: terom@25: /* terom@25: * The connection failed. terom@25: */ terom@25: static void _evsql_evpq_failure (struct evpq_conn *_conn, void *arg) { terom@25: struct evsql_conn *conn = arg; terom@21: terom@25: // just fail the conn terom@25: _evsql_conn_fail(conn); terom@21: } terom@21: terom@25: /* terom@25: * Our evpq behaviour terom@25: */ terom@21: static struct evpq_callback_info _evsql_evpq_cb_info = { terom@21: .fn_connected = _evsql_evpq_connected, terom@21: .fn_result = _evsql_evpq_result, terom@21: .fn_done = _evsql_evpq_done, terom@21: .fn_failure = _evsql_evpq_failure, terom@21: }; terom@21: terom@25: /* terom@25: * Allocate the generic evsql context. terom@25: */ terom@25: static struct evsql *_evsql_new_base (struct event_base *ev_base, evsql_error_cb error_fn, void *cb_arg) { terom@21: struct evsql *evsql = NULL; terom@21: terom@21: // allocate it terom@21: if ((evsql = calloc(1, sizeof(*evsql))) == NULL) terom@21: ERROR("calloc"); terom@21: terom@21: // store terom@25: evsql->ev_base = ev_base; terom@21: evsql->error_fn = error_fn; terom@21: evsql->cb_arg = cb_arg; terom@21: terom@21: // init terom@25: LIST_INIT(&evsql->conn_list); terom@25: TAILQ_INIT(&evsql->query_queue); terom@21: terom@21: // done terom@21: return evsql; terom@21: terom@21: error: terom@21: return NULL; terom@21: } terom@21: terom@25: /* terom@25: * Start a new connection and add it to the list, it won't be ready until _evsql_evpq_connected is called terom@25: */ terom@25: static struct evsql_conn *_evsql_conn_new (struct evsql *evsql) { terom@25: struct evsql_conn *conn = NULL; terom@25: terom@25: // allocate terom@25: if ((conn = calloc(1, sizeof(*conn))) == NULL) terom@25: ERROR("calloc"); terom@25: terom@25: // init terom@25: conn->evsql = evsql; terom@25: terom@25: // connect the engine terom@25: switch (evsql->type) { terom@25: case EVSQL_EVPQ: terom@25: if ((conn->engine.evpq = evpq_connect(evsql->ev_base, evsql->engine_conf.evpq, _evsql_evpq_cb_info, conn)) == NULL) terom@25: goto error; terom@25: terom@25: break; terom@25: terom@25: default: terom@25: FATAL("evsql->type"); terom@25: } terom@25: terom@25: // add it to the list terom@25: LIST_INSERT_HEAD(&evsql->conn_list, conn, entry); terom@25: terom@25: // success terom@25: return conn; terom@25: terom@25: error: terom@25: free(conn); terom@25: terom@25: return NULL; terom@25: } terom@25: terom@21: struct evsql *evsql_new_pq (struct event_base *ev_base, const char *pq_conninfo, evsql_error_cb error_fn, void *cb_arg) { terom@21: struct evsql *evsql = NULL; terom@21: terom@21: // base init terom@25: if ((evsql = _evsql_new_base (ev_base, error_fn, cb_arg)) == NULL) terom@21: goto error; terom@21: terom@25: // store conf terom@25: evsql->engine_conf.evpq = pq_conninfo; terom@25: terom@25: // pre-create one connection terom@25: if (_evsql_conn_new(evsql) == NULL) terom@21: goto error; terom@21: terom@21: // done terom@21: return evsql; terom@21: terom@21: error: terom@21: // XXX: more complicated than this? terom@21: free(evsql); terom@21: terom@21: return NULL; terom@21: } terom@21: terom@21: /* terom@25: * Checks if the connection is already allocated for some other trans/query. terom@21: * terom@21: * Returns: terom@25: * 0 connection idle, can be allocated terom@25: * >1 connection busy terom@21: */ terom@25: static int _evsql_conn_busy (struct evsql_conn *conn) { terom@25: // transactions get the connection to themselves terom@25: if (conn->trans) terom@25: return 1; terom@25: terom@25: // if it has a query assigned, it's busy terom@25: if (conn->query) terom@25: return 1; terom@25: terom@25: // otherwise, it's all idle terom@25: return 0; terom@25: } terom@25: terom@25: /* terom@25: * Checks if the connection is ready for use (i.e. _evsql_evpq_connected was called). terom@25: * terom@25: * The connection should not already have a query running. terom@25: * terom@25: * Returns terom@25: * <0 the connection is not valid (failed, query in progress) terom@25: * 0 the connection is still pending, and will become ready at some point terom@25: * >0 it's ready terom@25: */ terom@25: static int _evsql_conn_ready (struct evsql_conn *conn) { terom@25: switch (conn->evsql->type) { terom@21: case EVSQL_EVPQ: { terom@25: enum evpq_state state = evpq_state(conn->engine.evpq); terom@21: terom@21: switch (state) { terom@21: case EVPQ_CONNECT: terom@25: return 0; terom@21: terom@21: case EVPQ_CONNECTED: terom@25: return 1; terom@21: terom@25: case EVPQ_QUERY: terom@21: case EVPQ_INIT: terom@21: case EVPQ_FAILURE: terom@21: return -1; terom@21: terom@21: default: terom@25: FATAL("evpq_state: %d", state); terom@21: } terom@21: terom@21: } terom@21: terom@21: default: terom@25: FATAL("evsql->type: %d", conn->evsql->type); terom@21: } terom@21: } terom@21: terom@25: /* terom@25: * Allocate a connection for use and return it via *conn_ptr, or if may_queue is nonzero and the connection pool is terom@25: * getting full, return NULL (query should be queued). terom@25: * terom@25: * Note that the returned connection might not be ready for use yet (if we created a new one, see _evsql_conn_ready). terom@25: * terom@25: * Returns zero if a connection was found or the request should be queued, or nonzero if something failed and the terom@25: * request should be dropped. terom@25: */ terom@25: static int _evsql_conn_get (struct evsql *evsql, struct evsql_conn **conn_ptr, int may_queue) { terom@25: *conn_ptr = NULL; terom@25: terom@25: // find a connection that isn't busy and is ready (unless the query queue is empty). terom@25: LIST_FOREACH(*conn_ptr, &evsql->conn_list, entry) { terom@25: // skip busy conns always terom@25: if (_evsql_conn_busy(*conn_ptr)) terom@25: continue; terom@25: terom@25: // accept pending conns as long as there are NO enqueued queries (might cause deadlock otherwise) terom@25: if (_evsql_conn_ready(*conn_ptr) == 0 && TAILQ_EMPTY(&evsql->query_queue)) terom@25: break; terom@25: terom@25: // accept conns that are in a fully ready state terom@25: if (_evsql_conn_ready(*conn_ptr) > 0) terom@25: break; terom@25: } terom@25: terom@25: // if we found an idle connection, we can just return that right away terom@25: if (*conn_ptr) terom@25: return 0; terom@25: terom@25: // return NULL if may_queue and the conn list is not empty terom@25: if (may_queue && !LIST_EMPTY(&evsql->conn_list)) terom@25: return 0; terom@25: terom@25: // we need to open a new connection terom@25: if ((*conn_ptr = _evsql_conn_new(evsql)) == NULL) terom@25: goto error; terom@25: terom@25: // good terom@25: return 0; terom@25: error: terom@25: return -1; terom@25: } terom@25: terom@25: /* terom@25: * Validate and allocate the basic stuff for a new query. terom@25: */ terom@25: static struct evsql_query *_evsql_query_new (struct evsql *evsql, struct evsql_trans *trans, evsql_query_cb query_fn, void *cb_arg) { terom@21: struct evsql_query *query; terom@23: terom@25: // if it's part of a trans, then make sure the trans is idle terom@25: if (trans && trans->query) terom@25: ERROR("transaction is busy"); terom@25: terom@21: // allocate it terom@21: if ((query = calloc(1, sizeof(*query))) == NULL) terom@21: ERROR("calloc"); terom@21: terom@21: // store terom@21: query->cb_fn = query_fn; terom@21: query->cb_arg = cb_arg; terom@23: terom@23: // success terom@23: return query; terom@23: terom@23: error: terom@23: return NULL; terom@23: } terom@23: terom@25: /* terom@25: * Handle a new query. terom@25: * terom@25: * For transactions this will associate the query and then execute it, otherwise this will either find an idle terom@25: * connection and send the query, or enqueue it. terom@25: */ terom@25: static int _evsql_query_enqueue (struct evsql *evsql, struct evsql_trans *trans, struct evsql_query *query, const char *command) { terom@25: // transaction queries are handled differently terom@25: if (trans) { terom@25: // it's an in-transaction query terom@25: assert(trans->query == NULL); terom@25: terom@25: // assign the query terom@25: trans->query = query; terom@25: terom@25: // execute directly terom@25: if (_evsql_query_exec(trans->conn, query, command)) terom@25: goto error; terom@24: terom@24: } else { terom@25: struct evsql_conn *conn; terom@25: terom@25: // find an idle connection terom@25: if ((_evsql_conn_get(evsql, &conn, 1))) terom@25: ERROR("couldn't allocate a connection for the query"); terom@21: terom@25: // we must enqueue if no idle conn or the conn is not yet ready terom@25: if (conn && _evsql_conn_ready(conn) > 0) { terom@25: // execute directly terom@25: if (_evsql_query_exec(conn, query, command)) terom@25: goto error; terom@21: terom@25: terom@25: } else { terom@25: // copy the command for later execution terom@25: if ((query->command = strdup(command)) == NULL) terom@25: ERROR("strdup"); terom@25: terom@25: // enqueue until some connection pumps the queue terom@25: TAILQ_INSERT_TAIL(&evsql->query_queue, query, entry); terom@25: } terom@21: } terom@23: terom@23: // ok, good terom@23: return 0; terom@23: terom@23: error: terom@23: return -1; terom@23: } terom@23: terom@25: struct evsql_query *evsql_query (struct evsql *evsql, struct evsql_trans *trans, const char *command, evsql_query_cb query_fn, void *cb_arg) { terom@23: struct evsql_query *query = NULL; terom@21: terom@23: // alloc new query terom@25: if ((query = _evsql_query_new(evsql, trans, query_fn, cb_arg)) == NULL) terom@23: goto error; terom@23: terom@23: // just execute the command string directly terom@25: if (_evsql_query_enqueue(evsql, trans, query, command)) terom@23: goto error; terom@23: terom@23: // ok terom@21: return query; terom@21: terom@21: error: terom@23: _evsql_query_free(query); terom@21: terom@21: return NULL; terom@21: } terom@21: terom@25: 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) { terom@23: struct evsql_query *query = NULL; terom@24: const struct evsql_query_param *param; terom@23: int idx; terom@23: terom@23: // alloc new query terom@25: if ((query = _evsql_query_new(evsql, trans, query_fn, cb_arg)) == NULL) terom@23: goto error; terom@23: terom@23: // count the params terom@24: for (param = params->list; param->type; param++) terom@23: query->params.count++; terom@23: terom@23: // allocate the vertical storage for the parameters terom@23: if (0 terom@23: terom@23: // !(query->params.types = calloc(query->params.count, sizeof(Oid))) terom@23: || !(query->params.values = calloc(query->params.count, sizeof(char *))) terom@23: || !(query->params.lengths = calloc(query->params.count, sizeof(int))) terom@23: || !(query->params.formats = calloc(query->params.count, sizeof(int))) terom@23: ) terom@23: ERROR("calloc"); terom@23: terom@23: // transform terom@24: for (param = params->list, idx = 0; param->type; param++, idx++) { terom@23: // `types` stays NULL terom@23: // query->params.types[idx] = 0; terom@23: terom@23: // values terom@24: query->params.values[idx] = param->data_raw; terom@23: terom@24: // lengths terom@24: query->params.lengths[idx] = param->length; terom@23: terom@23: // formats, binary if length is nonzero terom@24: query->params.formats[idx] = param->length ? 1 : 0; terom@23: } terom@23: terom@23: // result format terom@24: switch (params->result_fmt) { terom@24: case EVSQL_FMT_TEXT: terom@24: query->params.result_format = 0; break; terom@24: terom@24: case EVSQL_FMT_BINARY: terom@24: query->params.result_format = 1; break; terom@24: terom@24: default: terom@24: FATAL("params.result_fmt: %d", params->result_fmt); terom@24: } terom@23: terom@23: // execute it terom@25: if (_evsql_query_enqueue(evsql, trans, query, command)) terom@23: goto error; terom@23: terom@23: // ok terom@23: return query; terom@23: terom@23: error: terom@23: _evsql_query_free(query); terom@23: terom@23: return NULL; terom@23: } terom@23: