/* 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 #include #ifdef HAVE_LIBPQ_FE_H #include #elif defined(HAVE_POSTGRESQL_LIBPQ_FE_H) #include #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