diff options
author | Lucas Eckels <eckels@google.com> | 2012-08-06 14:57:33 -0700 |
---|---|---|
committer | Lucas Eckels <eckels@google.com> | 2012-08-08 09:28:26 -0700 |
commit | d3b3634aa7fa05dc9659f3cc20353ef0d2f2b715 (patch) | |
tree | 2f1af772d781cbc6011a7b185ec654ffcb5101b5 /dbd/apr_dbd_pgsql.c | |
parent | 94feb8a3d7689c740c147abef408b22c6c6fd65d (diff) | |
download | apache-apr-util-d3b3634aa7fa05dc9659f3cc20353ef0d2f2b715.tar.gz |
Add apache-apr-util 1.2.12 source.
Change-Id: Ia2f9f785cfd65902f61b0cccf1e01840e2f4f90c
Diffstat (limited to 'dbd/apr_dbd_pgsql.c')
-rw-r--r-- | dbd/apr_dbd_pgsql.c | 664 |
1 files changed, 664 insertions, 0 deletions
diff --git a/dbd/apr_dbd_pgsql.c b/dbd/apr_dbd_pgsql.c new file mode 100644 index 0000000..2fc28ac --- /dev/null +++ b/dbd/apr_dbd_pgsql.c @@ -0,0 +1,664 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "apu.h" + +#if APU_HAVE_PGSQL + +#include "apu_config.h" + +#include <ctype.h> +#include <stdlib.h> + +#ifdef HAVE_LIBPQ_FE_H +#include <libpq-fe.h> +#elif defined(HAVE_POSTGRESQL_LIBPQ_FE_H) +#include <postgresql/libpq-fe.h> +#endif + +#include "apr_strings.h" +#include "apr_time.h" + +#include "apr_dbd_internal.h" + +#define QUERY_MAX_ARGS 40 + +struct apr_dbd_transaction_t { + int errnum; + apr_dbd_t *handle; +}; + +struct apr_dbd_t { + PGconn *conn; + apr_dbd_transaction_t *trans; +}; + +struct apr_dbd_results_t { + int random; + PGconn *handle; + PGresult *res; + size_t ntuples; + size_t sz; + size_t index; +}; + +struct apr_dbd_row_t { + int n; + apr_dbd_results_t *res; +}; + +struct apr_dbd_prepared_t { + const char *name; + int prepared; + int nargs; +}; + +#define dbd_pgsql_is_success(x) (((x) == PGRES_EMPTY_QUERY) \ + || ((x) == PGRES_COMMAND_OK) \ + || ((x) == PGRES_TUPLES_OK)) + +static apr_status_t clear_result(void *data) +{ + PQclear(data); + return APR_SUCCESS; +} + +static int dbd_pgsql_select(apr_pool_t *pool, apr_dbd_t *sql, + apr_dbd_results_t **results, + const char *query, int seek) +{ + PGresult *res; + int ret; + if ( sql->trans && sql->trans->errnum ) { + return sql->trans->errnum; + } + if (seek) { /* synchronous query */ + res = PQexec(sql->conn, query); + if (res) { + ret = PQresultStatus(res); + if (dbd_pgsql_is_success(ret)) { + ret = 0; + } else { + PQclear(res); + } + } else { + ret = PGRES_FATAL_ERROR; + } + if (ret != 0) { + if (sql->trans) { + sql->trans->errnum = ret; + } + return ret; + } + if (!*results) { + *results = apr_pcalloc(pool, sizeof(apr_dbd_results_t)); + } + (*results)->res = res; + (*results)->ntuples = PQntuples(res); + (*results)->sz = PQnfields(res); + (*results)->random = seek; + apr_pool_cleanup_register(pool, res, clear_result, + apr_pool_cleanup_null); + } + else { + if (PQsendQuery(sql->conn, query) == 0) { + if (sql->trans) { + sql->trans->errnum = 1; + } + return 1; + } + if (*results == NULL) { + *results = apr_pcalloc(pool, sizeof(apr_dbd_results_t)); + } + (*results)->random = seek; + (*results)->handle = sql->conn; + } + return 0; +} + +static int dbd_pgsql_get_row(apr_pool_t *pool, apr_dbd_results_t *res, + apr_dbd_row_t **rowp, int rownum) +{ + apr_dbd_row_t *row = *rowp; + int sequential = ((rownum >= 0) && res->random) ? 0 : 1; + + if (row == NULL) { + row = apr_palloc(pool, sizeof(apr_dbd_row_t)); + *rowp = row; + row->res = res; + row->n = sequential ? 0 : rownum; + } + else { + if ( sequential ) { + ++row->n; + } + else { + row->n = rownum; + } + } + + if (res->random) { + if (row->n >= res->ntuples) { + *rowp = NULL; + apr_pool_cleanup_run(pool, res->res, clear_result); + res->res = NULL; + return -1; + } + } + else { + if (row->n >= res->ntuples) { + /* no data; we have to fetch some */ + row->n -= res->ntuples; + if (res->res != NULL) { + PQclear(res->res); + } + res->res = PQgetResult(res->handle); + if (res->res) { + res->ntuples = PQntuples(res->res); + while (res->ntuples == 0) { + /* if we got an empty result, clear it, wait a mo, try + * again */ + PQclear(res->res); + apr_sleep(100000); /* 0.1 secs */ + res->res = PQgetResult(res->handle); + if (res->res) { + res->ntuples = PQntuples(res->res); + } + else { + return -1; + } + } + if (res->sz == 0) { + res->sz = PQnfields(res->res); + } + } + else { + return -1; + } + } + } + return 0; +} + +static const char *dbd_pgsql_get_entry(const apr_dbd_row_t *row, int n) +{ + return PQgetvalue(row->res->res, row->n, n); +} + +static const char *dbd_pgsql_error(apr_dbd_t *sql, int n) +{ + return PQerrorMessage(sql->conn); +} + +static int dbd_pgsql_query(apr_dbd_t *sql, int *nrows, const char *query) +{ + PGresult *res; + int ret; + if (sql->trans && sql->trans->errnum) { + return sql->trans->errnum; + } + res = PQexec(sql->conn, query); + if (res) { + ret = PQresultStatus(res); + if (dbd_pgsql_is_success(ret)) { + /* ugh, making 0 return-success doesn't fit */ + ret = 0; + } + *nrows = atoi(PQcmdTuples(res)); + PQclear(res); + } + else { + ret = PGRES_FATAL_ERROR; + } + if (sql->trans) { + sql->trans->errnum = ret; + } + return ret; +} + +static const char *dbd_pgsql_escape(apr_pool_t *pool, const char *arg, + apr_dbd_t *sql) +{ + size_t len = strlen(arg); + char *ret = apr_palloc(pool, 2*(len + 1)); + PQescapeString(ret, arg, len); + return ret; +} + +static int dbd_pgsql_prepare(apr_pool_t *pool, apr_dbd_t *sql, + const char *query, const char *label, + apr_dbd_prepared_t **statement) +{ + char *sqlcmd; + char *sqlptr; + size_t length; + size_t i = 0; + const char *args[QUERY_MAX_ARGS]; + size_t alen; + int ret; + PGresult *res; + char *pgquery; + char *pgptr; + + if (!*statement) { + *statement = apr_palloc(pool, sizeof(apr_dbd_prepared_t)); + } + (*statement)->nargs = 0; + /* Translate from apr_dbd to native query format */ + for (sqlptr = (char*)query; *sqlptr; ++sqlptr) { + if (sqlptr[0] == '%') { + if (isalpha(sqlptr[1])) { + ++(*statement)->nargs; + } + else if (sqlptr[1] == '%') { + ++sqlptr; + } + } + } + length = strlen(query) + 1; + if ((*statement)->nargs > 8) { + length += (*statement)->nargs - 8; + } + pgptr = pgquery = apr_palloc(pool, length) ; + + for (sqlptr = (char*)query; *sqlptr; ++sqlptr) { + if ((sqlptr[0] == '%') && isalpha(sqlptr[1])) { + *pgptr++ = '$'; + if (i < 9) { + *pgptr++ = '1' + i; + } + else { + *pgptr++ = '0' + ((i+1)/10); + *pgptr++ = '0' + ((i+1)%10); + } + switch (*++sqlptr) { + case 'd': + args[i] = "integer"; + break; + case 's': + args[i] = "varchar"; + break; + default: + args[i] = "varchar"; + break; + } + length += 1 + strlen(args[i]); + ++i; + } + else if ((sqlptr[0] == '%') && (sqlptr[1] == '%')) { + /* reduce %% to % */ + *pgptr++ = *sqlptr++; + } + else { + *pgptr++ = *sqlptr; + } + } + *pgptr = 0; + + if (!label) { + /* don't really prepare; use in execParams instead */ + (*statement)->prepared = 0; + (*statement)->name = apr_pstrdup(pool, pgquery); + return 0; + } + (*statement)->name = apr_pstrdup(pool, label); + + /* length of SQL query that prepares this statement */ + length = 8 + strlen(label) + 2 + 4 + length + 1; + sqlcmd = apr_palloc(pool, length); + sqlptr = sqlcmd; + memcpy(sqlptr, "PREPARE ", 8); + sqlptr += 8; + length = strlen(label); + memcpy(sqlptr, label, length); + sqlptr += length; + if ((*statement)->nargs > 0) { + memcpy(sqlptr, " (",2); + sqlptr += 2; + for (i=0; i < (*statement)->nargs; ++i) { + alen = strlen(args[i]); + memcpy(sqlptr, args[i], alen); + sqlptr += alen; + *sqlptr++ = ','; + } + sqlptr[-1] = ')'; + } + memcpy(sqlptr, " AS ", 4); + sqlptr += 4; + memcpy(sqlptr, pgquery, strlen(pgquery)); + sqlptr += strlen(pgquery); + *sqlptr = 0; + + res = PQexec(sql->conn, sqlcmd); + if ( res ) { + ret = PQresultStatus(res); + if (dbd_pgsql_is_success(ret)) { + ret = 0; + } + /* Hmmm, do we do this here or register it on the pool? */ + PQclear(res); + } + else { + ret = PGRES_FATAL_ERROR; + } + (*statement)->prepared = 1; + + return ret; +} + +static int dbd_pgsql_pquery(apr_pool_t *pool, apr_dbd_t *sql, + int *nrows, apr_dbd_prepared_t *statement, + int nargs, const char **values) +{ + int ret; + PGresult *res; + + if (sql->trans && sql->trans->errnum) { + return sql->trans->errnum; + } + + if (statement->prepared) { + res = PQexecPrepared(sql->conn, statement->name, nargs, values, 0, 0, + 0); + } + else { + res = PQexecParams(sql->conn, statement->name, nargs, 0, values, 0, 0, + 0); + } + if (res) { + ret = PQresultStatus(res); + if (dbd_pgsql_is_success(ret)) { + ret = 0; + } + *nrows = atoi(PQcmdTuples(res)); + PQclear(res); + } + else { + ret = PGRES_FATAL_ERROR; + } + + if (sql->trans) { + sql->trans->errnum = ret; + } + return ret; +} + +static int dbd_pgsql_pvquery(apr_pool_t *pool, apr_dbd_t *sql, + int *nrows, apr_dbd_prepared_t *statement, + va_list args) +{ + const char **values; + int i; + + if (sql->trans && sql->trans->errnum) { + return sql->trans->errnum; + } + + values = apr_palloc(pool, sizeof(*values) * statement->nargs); + + for (i = 0; i < statement->nargs; i++) { + values[i] = apr_pstrdup(pool, va_arg(args, const char*)); + } + + return dbd_pgsql_pquery(pool, sql, nrows, statement, + statement->nargs, values); +} + +static int dbd_pgsql_pselect(apr_pool_t *pool, apr_dbd_t *sql, + apr_dbd_results_t **results, + apr_dbd_prepared_t *statement, + int seek, int nargs, const char **values) +{ + PGresult *res; + int rv; + int ret = 0; + + if (sql->trans && sql->trans->errnum) { + return sql->trans->errnum; + } + + if (seek) { /* synchronous query */ + if (statement->prepared) { + res = PQexecPrepared(sql->conn, statement->name, nargs, values, 0, + 0, 0); + } + else { + res = PQexecParams(sql->conn, statement->name, nargs, 0, values, 0, + 0, 0); + } + if (res) { + ret = PQresultStatus(res); + if (dbd_pgsql_is_success(ret)) { + ret = 0; + } + else { + PQclear(res); + } + } + else { + ret = PGRES_FATAL_ERROR; + } + if (ret != 0) { + if (sql->trans) { + sql->trans->errnum = ret; + } + return ret; + } + if (!*results) { + *results = apr_pcalloc(pool, sizeof(apr_dbd_results_t)); + } + (*results)->res = res; + (*results)->ntuples = PQntuples(res); + (*results)->sz = PQnfields(res); + (*results)->random = seek; + apr_pool_cleanup_register(pool, res, clear_result, + apr_pool_cleanup_null); + } + else { + if (statement->prepared) { + rv = PQsendQueryPrepared(sql->conn, statement->name, nargs, values, + 0, 0, 0); + } + else { + rv = PQsendQueryParams(sql->conn, statement->name, nargs, 0, + values, 0, 0, 0); + } + if (rv == 0) { + if (sql->trans) { + sql->trans->errnum = 1; + } + return 1; + } + if (!*results) { + *results = apr_pcalloc(pool, sizeof(apr_dbd_results_t)); + } + (*results)->random = seek; + (*results)->handle = sql->conn; + } + + if (sql->trans) { + sql->trans->errnum = ret; + } + return ret; +} + +static int dbd_pgsql_pvselect(apr_pool_t *pool, apr_dbd_t *sql, + apr_dbd_results_t **results, + apr_dbd_prepared_t *statement, + int seek, va_list args) +{ + const char **values; + int i; + + if (sql->trans && sql->trans->errnum) { + return sql->trans->errnum; + } + + values = apr_palloc(pool, sizeof(*values) * statement->nargs); + + for (i = 0; i < statement->nargs; i++) { + values[i] = apr_pstrdup(pool, va_arg(args, const char*)); + } + + return dbd_pgsql_pselect(pool, sql, results, statement, + seek, statement->nargs, values) ; +} + +static int dbd_pgsql_start_transaction(apr_pool_t *pool, apr_dbd_t *handle, + apr_dbd_transaction_t **trans) +{ + int ret = 0; + PGresult *res; + + /* XXX handle recursive transactions here */ + + res = PQexec(handle->conn, "BEGIN TRANSACTION"); + if (res) { + ret = PQresultStatus(res); + if (dbd_pgsql_is_success(ret)) { + ret = 0; + if (!*trans) { + *trans = apr_pcalloc(pool, sizeof(apr_dbd_transaction_t)); + } + } + PQclear(res); + (*trans)->handle = handle; + handle->trans = *trans; + } + else { + ret = PGRES_FATAL_ERROR; + } + return ret; +} + +static int dbd_pgsql_end_transaction(apr_dbd_transaction_t *trans) +{ + PGresult *res; + int ret = -1; /* no transaction is an error cond */ + if (trans) { + if (trans->errnum) { + trans->errnum = 0; + res = PQexec(trans->handle->conn, "ROLLBACK"); + } + else { + res = PQexec(trans->handle->conn, "COMMIT"); + } + if (res) { + ret = PQresultStatus(res); + if (dbd_pgsql_is_success(ret)) { + ret = 0; + } + PQclear(res); + } + else { + ret = PGRES_FATAL_ERROR; + } + trans->handle->trans = NULL; + } + return ret; +} + +static apr_dbd_t *dbd_pgsql_open(apr_pool_t *pool, const char *params) +{ + apr_dbd_t *sql; + + PGconn *conn = PQconnectdb(params); + + /* if there's an error in the connect string or something we get + * back a * bogus connection object, and things like PQreset are + * liable to segfault, so just close it out now. it would be nice + * if we could give an indication of why we failed to connect... */ + if (PQstatus(conn) != CONNECTION_OK) { + PQfinish(conn); + return NULL; + } + + sql = apr_pcalloc (pool, sizeof (*sql)); + + sql->conn = conn; + + return sql; +} + +static apr_status_t dbd_pgsql_close(apr_dbd_t *handle) +{ + PQfinish(handle->conn); + return APR_SUCCESS; +} + +static apr_status_t dbd_pgsql_check_conn(apr_pool_t *pool, + apr_dbd_t *handle) +{ + if (PQstatus(handle->conn) != CONNECTION_OK) { + PQreset(handle->conn); + if (PQstatus(handle->conn) != CONNECTION_OK) { + return APR_EGENERAL; + } + } + return APR_SUCCESS; +} + +static int dbd_pgsql_select_db(apr_pool_t *pool, apr_dbd_t *handle, + const char *name) +{ + return APR_ENOTIMPL; +} + +static void *dbd_pgsql_native(apr_dbd_t *handle) +{ + return handle->conn; +} + +static int dbd_pgsql_num_cols(apr_dbd_results_t* res) +{ + return res->sz; +} + +static int dbd_pgsql_num_tuples(apr_dbd_results_t* res) +{ + if (res->random) { + return res->ntuples; + } + else { + return -1; + } +} + +APU_DECLARE_DATA const apr_dbd_driver_t apr_dbd_pgsql_driver = { + "pgsql", + NULL, + dbd_pgsql_native, + dbd_pgsql_open, + dbd_pgsql_check_conn, + dbd_pgsql_close, + dbd_pgsql_select_db, + dbd_pgsql_start_transaction, + dbd_pgsql_end_transaction, + dbd_pgsql_query, + dbd_pgsql_select, + dbd_pgsql_num_cols, + dbd_pgsql_num_tuples, + dbd_pgsql_get_row, + dbd_pgsql_get_entry, + dbd_pgsql_error, + dbd_pgsql_escape, + dbd_pgsql_prepare, + dbd_pgsql_pvquery, + dbd_pgsql_pvselect, + dbd_pgsql_pquery, + dbd_pgsql_pselect, +}; +#endif |