On Mon, 6 Dec 2004, Nick Kew wrote:
> Currently I have code for a proposed apr_dbd that compiles and loads
> cleanly, comprising apr_dbd.[c|h], a MySQL driver apr_dbd_mysql,
I've now added a test program, and the drivers for MySQL and PostgreSQL
work for me. Time to post here for preliminary review.
I attach the current candidate APR software:
apr_dbd.h - specifies the API
apr_dbd.c - implements (skeleton) framework
apr_dbd_mysql.c - driver for MySQL
apr_dbd_pgsql.c - driver for PostgreSQL
apr_dbd_test.c - test program
There's a package containing this together with some related stuff
at http://www.apache.org/~niq/apache-dbd.tar.bz2
If it is accepted as a startingpoint for an apr-util module, I'll
donate it to ASF and license it all under ASF terms.
If not, all rights reserved.
The Prepared Statement stuff is ugly: do any of the Perl folks know
how Perl DBI deals with different syntaxes and backends, and is there
anything we can usefully steal?
--
Nick Kew
/* Copyright (c) 2004, Nick Kew. All rights reserved.
If this is accepted by ASF for inclusion in APR-UTIL,
I will assign copyright to ASF and license under ASF terms.
Otherwise I will retain copyright and license under
terms of my choice.
*/
#ifndef APR_DBD_H
#define APR_DBD_H
#ifdef __cplusplus
extern "C" {
#endif
/** Patch required to build against APR/0.9 and HTTPD/2.0
* Invalidate a resource in the pool - e.g. a database connection
* that returns a "lost connection" error and can't be restored.
* Use this instead of apr_reslist_release if the resource is bad.
*/
#if APR_HAS_THREADS
#include "apr_reslist.h"
APU_DECLARE(apr_status_t) apr_reslist_invalidate(apr_reslist_t *reslist,
void *resource);
#endif
/* These are opaque structs. Instantiation is up to each backend */
#ifndef APR_DBD_INTERNAL
typedef struct apr_dbd_t apr_dbd_t;
typedef struct apr_dbd_transaction apr_dbd_transaction;
typedef struct apr_dbd_results apr_dbd_results;
typedef struct apr_dbd_row apr_dbd_row;
typedef struct apr_dbd_prepared apr_dbd_prepared;
#endif
typedef struct apr_dbd_driver_t {
/** name */
const char* name;
/** native_handle: return the native database handle of the underlying db
*
* @param handle - apr_dbd handle
* @return - native handle
*/
void *(*native_handle)(apr_dbd_t *handle);
/** open: obtain a database connection from the server rec.
* Must be explicitly closed when you're finished with it.
* WARNING: only use this when you need a connection with
* a lifetime other than a request
*
* @param pool - a pool to use for error messages (if any).
* @param s - server rec managing the underlying connection/pool.
* @return database handle, or NULL on error.
*/
apr_dbd_t *(*open)(apr_pool_t *pool, const char *params);
/** check_conn: check status of a database connection
*
* @param pool - a pool to use for error messages (if any).
* @param handle - the connection to check
* @return APR_SUCCESS or error
*/
apr_status_t (*check_conn)(apr_pool_t *pool, apr_dbd_t *handle);
/** close: close/release a connection obtained from open()
*
* @param handle - the connection to release
* @return APR_SUCCESS or error
*/
apr_status_t (*close)(void *handle);
/** set_dbname: select database name. May be a no-op if not supported.
*
* @param pool - working pool
* @param handle - the connection
* @param name - the database to select
* @return 0 for success or error code
*/
int (*set_dbname)(apr_pool_t* pool, apr_dbd_t *handle, const char *name);
/** transaction: start a transaction. May be a no-op.
*
* @param pool - a pool to use for error messages (if any).
* @param handle - the connection to check
* @param transaction - ptr to a transaction. May be null on entry
* @return 0 for success or error code
*/
int (*transaction)(apr_pool_t *pool, apr_dbd_t *handle,
apr_dbd_transaction **trans);
/** end_transaction: end a transaction
* (commit on success, rollback on error).
* May be a no-op.
*
* @param handle - the connection to check
* @param transaction - the transaction.
* @return 0 for success or error code
*/
int (*end_transaction)(apr_dbd_t *handle, apr_dbd_transaction *trans);
/** query: execute an SQL query that doesn't return a result set
*
* @param handle - the connection
* @param transaction - current transaction. May be null.
* @param nrows - number of rows affected.
* @param statement - the SQL statement to execute
* @return 0 for success or error code
*/
int (*query)(apr_dbd_t *handle, apr_dbd_transaction *trans,
int *nrows, const char *statement);
/** select: execute an SQL query that returns a result set
*
* @param pool - pool to allocate the result set
* @param handle - the connection
* @param transaction - current transaction. May be null.
* @param res - pointer to result set pointer. May point to NULL on entry
* @param statement - the SQL statement to execute
* @param random - 1 to support random access to results (seek any row);
* 0 to support only looping through results in order
* (async access - faster)
* @return 0 for success or error code
*/
int (*select)(apr_pool_t *pool, apr_dbd_t *handle,
apr_dbd_transaction *trans, apr_dbd_results **res,
const char *statement, int random);
/** num_cols: get the number of columns in a results set
*
* @param res - result set.
* @return number of columns
*/
int (*num_cols)(apr_dbd_results *res);
/** num_tuples: get the number of rows in a results set
* of a synchronous select
*
* @param res - result set.
* @return number of rows, or -1 if the results are asynchronous
*/
int (*num_tuples)(apr_dbd_results *res);
/** get_row: get a row from a result set
*
* @param pool - pool to allocate the row
* @param res - result set pointer
* @param row - pointer to row pointer. May point to NULL on entry
* @param rownum - row number, or -1 for "next row". Ignored if random
* access is not supported.
* @return 0 for success, -1 for rownum out of range or data finished
*/
int (*get_row)(apr_pool_t *pool, apr_dbd_results *res,
apr_dbd_row **row, int rownum);
/** get_entry: get an entry from a row
*
* @param row - row pointer
* @param col - entry number
* @return value from the row, or NULL if col is out of bounds.
*/
const char *(*get_entry)(const apr_dbd_row *row, int col);
/** error: get current error message (if any)
*
* @param handle - the connection
* @param errnum - error code from operation that returned an error
* @return the database current error message, or message for errnum
* (implementation-dependent whether errnum is ignored)
*/
const char *(*error)(apr_dbd_t *handle, int errnum);
/** escape: escape a string so it is safe for use in query/select
*
* @param pool - pool to alloc the result from
* @param string - the string to escape
* @param handle - the connection
* @return the escaped, safe string
*/
const char *(*escape)(apr_pool_t *pool, const char *string,
apr_dbd_t *handle);
/** prepare: prepare a statement
*
* @param pool - pool to alloc the result from
* @param handle - the connection
* @param query - the SQL query
* @param label - A label for the prepared statement.
* use NULL for temporary prepared statements
* (eg within a Request in httpd)
* @param statement - statement to prepare. May point to null on entry.
* @return 0 for success or error code
*/
int (*prepare)(apr_pool_t *pool, apr_dbd_t *handle, const char *query,
const char *label, apr_dbd_prepared **statement);
/** pquery: query using a prepared statement + args
*
* @param pool - working pool
* @param handle - the connection
* @param trans - current transaction. May be null.
* @param nrows - number of rows affected.
* @param statement - the prepared statement to execute
* @param ... - args to prepared statement
* @return 0 for success or error code
*/
int (*pquery)(apr_pool_t *pool, apr_dbd_t *handle,
apr_dbd_transaction *trans, int *nrows,
apr_dbd_prepared *statement, ...);
/** pselect: select using a prepared statement + args
*
* @param pool - working pool
* @param handle - the connection
* @param trans - current transaction. May be null.
* @param res - pointer to query results. May point to NULL on entry
* @param statement - the prepared statement to execute
* @param random - Whether to support random-access to results
* @param ... - args to prepared statement
* @return 0 for success or error code
*/
int (*pselect)(apr_pool_t *pool, apr_dbd_t *handle,
apr_dbd_transaction *trans, apr_dbd_results **res,
apr_dbd_prepared *statement, int random, ...);
} apr_dbd_driver_t;
/** apr_dbd_get_driver: get the driver struct for a name
*
* @param name - driver name
* @return driver struct for name, or NULL if not implemented
*/
APU_DECLARE(apr_dbd_driver_t*) apr_dbd_get_driver(const char *name);
/** apr_dbd_open: open a connection to a backend
*
* @param pool - pool to register a cleanup on. May be NULL.
* @param ptmp - working pool
* @param name - driver name. Ignored if driver doesn't point to NULL
* @param params - arguments to driver (implementation-dependent)
* @param handle - pointer to handle to return
* @param driver - pointer to driver struct. May point to NULL
* in which case name is used to lookup & return it
* @return APR_SUCCESS for success
* @return APR_ENOTIMPL for no driver
* @return APR_EGENERAL if driver exists but connection failed
*/
APU_DECLARE(apr_status_t) apr_dbd_open(apr_pool_t *pool, apr_pool_t *ptmp,
const char *name, const char *params,
apr_dbd_t **handle,
apr_dbd_driver_t **driver);
/** apr_dbd_close: close a connection to a backend.
* Only required for explicit close or
* if first pool arg to apr_dbd_open was NULL
*
* @param pool - Must be same pool as (first) pool arg to apr_dbd_open
* @param handle - handle to close
* @param driver - driver struct.
* @return APR_SUCCESS for success or error status
*/
APU_DECLARE(apr_status_t) apr_dbd_close(apr_pool_t *pool, apr_dbd_t *handle,
apr_dbd_driver_t *driver);
/* apr-function-shaped versions of things */
/** apr_dbd_name: get the name of the driver
*
* @param driver - the driver
* @return - name
*/
#define apr_dbd_name(driver) \
(driver)->name
/** apr_dbd_native_handle: get native database handle of the underlying db
*
* @param driver - the driver
* @param handle - apr_dbd handle
* @return - native handle
*/
#define apr_dbd_native_handle(driver,handler) \
(driver)->native_handle(handler)
/** check_conn: check status of a database connection
*
* @param driver - the driver
* @param pool - working pool
* @param handle - the connection to check
* @return APR_SUCCESS or error
*/
#define apr_dbd_check_conn(driver,pool,handle) \
(driver)->check_conn((pool),(handle))
/** apr_dbd_set_dbname: select database name. May be a no-op if not supported.
*
* @param driver - the driver
* @param pool - working pool
* @param handle - the connection
* @param name - the database to select
* @return 0 for success or error code
*/
#define apr_dbd_set_dbname(driver,pool,handle,name) \
(driver)->set_dbname((pool),(handle),(name))
/** apr_dbd_transaction_start: start a transaction. May be a no-op.
*
* @param driver - the driver
* @param pool - a pool to use for error messages (if any).
* @param handle - the connection to check
* @param transaction - ptr to a transaction. May be null on entry
* @return 0 for success or error code
*/
#define apr_dbd_transaction_start(driver,pool,handle,trans) \
(driver)->transaction((pool),(handle),(trans))
/** apr_dbd_transaction_end: end a transaction
* (commit on success, rollback on error).
* May be a no-op.
*
* @param driver - the driver
* @param handle - the connection to check
* @param transaction - the transaction.
* @return 0 for success or error code
*/
#define apr_dbd_transaction_end(driver,handle,trans) \
(driver)->end_transaction((handle),(trans))
/** apr_dbd_query: execute an SQL query that doesn't return a result set
*
* @param driver - the driver
* @param handle - the connection
* @param transaction - current transaction. May be null.
* @param nrows - number of rows affected.
* @param statement - the SQL statement to execute
* @return 0 for success or error code
*/
#define apr_dbd_query(driver,handle,trans,nrows,statement) \
(driver)->query((handle),(trans),(nrows),(statement))
/** apr_dbd_select: execute an SQL query that returns a result set
*
* @param driver - the driver
* @param pool - pool to allocate the result set
* @param handle - the connection
* @param transaction - current transaction. May be null.
* @param res - pointer to result set pointer. May point to NULL on entry
* @param statement - the SQL statement to execute
* @param random - 1 to support random access to results (seek any row);
* 0 to support only looping through results in order
* (async access - faster)
* @return 0 for success or error code
*/
#define apr_dbd_select(driver,pool,handle,trans,res,statement,random) \
(driver)->select((pool),(handle),(trans),(res),(statement),(random))
/** apr_dbd_num_cols: get the number of columns in a results set
*
* @param driver - the driver
* @param res - result set.
* @return number of columns
*/
#define apr_dbd_num_cols(driver,res) \
(driver)->num_cols((res))
/** apr_dbd_num_tuples: get the number of rows in a results set
* of a synchronous select
*
* @param driver - the driver
* @param res - result set.
* @return number of rows, or -1 if the results are asynchronous
*/
#define apr_dbd_num_tuples(driver,res) \
(driver)->num_tuples((res))
/** apr_dbd_get_row: get a row from a result set
*
* @param driver - the driver
* @param pool - pool to allocate the row
* @param res - result set pointer
* @param row - pointer to row pointer. May point to NULL on entry
* @param rownum - row number, or -1 for "next row". Ignored if random
* access is not supported.
* @return 0 for success, -1 for rownum out of range or data finished
*/
#define apr_dbd_get_row(driver,pool,res,row,rownum) \
(driver)->get_row((pool),(res),(row),(rownum))
/** apr_dbd_get_entry: get an entry from a row
*
* @param driver - the driver
* @param row - row pointer
* @param col - entry number
* @return value from the row, or NULL if col is out of bounds.
*/
#define apr_dbd_get_entry(driver,row,col) \
(driver)->get_entry((row),(col))
/** apr_dbd_error: get current error message (if any)
*
* @param driver - the driver
* @param handle - the connection
* @param errnum - error code from operation that returned an error
* @return the database current error message, or message for errnum
* (implementation-dependent whether errnum is ignored)
*/
#define apr_dbd_error(driver,handle,errnum) \
(driver)->error((handle),(errnum))
/** apr_dbd_escape: escape a string so it is safe for use in query/select
*
* @param driver - the driver
* @param pool - pool to alloc the result from
* @param string - the string to escape
* @param handle - the connection
* @return the escaped, safe string
*/
#define apr_dbd_escape(driver,pool,string,handle) \
(driver)->escape((pool),(string),(handle))
/** apr_dbd_prepare: prepare a statement
*
* @param driver - the driver
* @param pool - pool to alloc the result from
* @param handle - the connection
* @param query - the SQL query
* @param label - A label for the prepared statement.
* use NULL for temporary prepared statements
* (eg within a Request in httpd)
* @param statement - statement to prepare. May point to null on entry.
* @return 0 for success or error code
*/
#define apr_dbd_prepare(driver,pool,handle,query,label,statement) \
(driver)->prepare((pool),(handle),(query),(label),(statement))
/* need macros that do varargs to deal with pquery and pselect :-) */
#ifdef __cplusplus
}
#endif
#endif
/* Copyright (c) 2004, Nick Kew. All rights reserved.
If this is accepted by ASF for inclusion in APR-UTIL,
I will assign copyright to ASF and license under ASF terms.
Otherwise I will retain copyright and license under
terms of my choice.
*/
#include "apu.h"
#include "apr_pools.h"
#include "apr_dbd.h"
/** FIXME
* I've implemented MySQL and PostgreSQL.
* Others are placeholders for when someone gets around to it.
* I know there are many candidates I've overlooked - add them at will.
*/
#if APU_HAVE_MYSQL
extern apr_dbd_driver_t apr_dbd_mysql_driver;
#endif
#if APU_HAVE_PGSQL
extern apr_dbd_driver_t apr_dbd_pgsql_driver;
#endif
#if APU_HAVE_FIREBIRD
extern apr_dbd_driver_t apr_dbd_firebird_driver;
#endif
#if APU_HAVE_MSQL
extern apr_dbd_driver_t apr_dbd_msql_driver;
#endif
#if APU_HAVE_ODBC
extern apr_dbd_driver_t apr_dbd_odbc_driver;
#endif
#if APU_HAVE_MSSQL
extern apr_dbd_driver_t apr_dbd_mssql_driver;
#endif
#if APU_HAVE_ORACLE
extern apr_dbd_driver_t apr_dbd_oracle_driver;
#endif
static struct {
const char *name;
apr_dbd_driver_t *driver;
} apr_dbd_drivers[] = {
#if APU_HAVE_MYSQL
{ "mysql", &apr_dbd_mysql_driver } ,
#endif
#if APU_HAVE_PGSQL
{ "pgsql", &apr_dbd_pgsql_driver } ,
#endif
#if APU_HAVE_FIREBIRD
{ "firebird", &apr_dbd_firebird_driver } ,
#endif
#if APU_HAVE_MSQL
{ "msql", &apr_dbd_msql_driver } ,
#endif
#if APU_HAVE_ODBC
{ "odbc", &apr_dbd_odbc_driver } ,
#endif
#if APU_HAVE_ORACLE
{ "oracle", &apr_dbd_oracle_driver } ,
#endif
#if APU_HAVE_MSSQL
{ "mssql", &apr_dbd_mssql_driver } ,
#endif
{ NULL , NULL }
};
APU_DECLARE(apr_dbd_driver_t*) apr_dbd_get_driver(const char *name)
{
int i;
for (i = 0; apr_dbd_drivers[i].name != NULL; ++i) {
if (!strcasecmp(apr_dbd_drivers[i].name, name)) {
return apr_dbd_drivers[i].driver;
}
}
return NULL;
}
APU_DECLARE(apr_status_t) apr_dbd_open(apr_pool_t *pool,
apr_pool_t *ptmp,
const char *name,
const char *params,
apr_dbd_t **handle,
apr_dbd_driver_t **driver)
{
apr_status_t rv = APR_SUCCESS;
if (*driver == NULL) {
*driver = apr_dbd_get_driver(name);
if (*driver == NULL) {
return APR_ENOTIMPL;
}
}
*handle = (*driver)->open(ptmp, params);
if (*handle == NULL) {
return APR_EGENERAL;
}
rv = apr_dbd_check_conn(*driver, ptmp, *handle);
if (rv == 0) {
if (pool != NULL) {
apr_pool_cleanup_register(pool, *handle, (*driver)->close,
apr_pool_cleanup_null);
}
}
else {
(*driver)->close(*handle);
rv = APR_EGENERAL;
}
return rv;
}
APU_DECLARE(apr_status_t) apr_dbd_close(apr_pool_t *pool, apr_dbd_t *handle,
apr_dbd_driver_t *driver)
{
if (pool != NULL) {
apr_pool_cleanup_kill(pool, handle, driver->close);
}
return driver->close(handle);
}
/*
Copyright (c) 2003-4, WebThing Ltd
Author: Nick Kew <[EMAIL PROTECTED]>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
/*
If this is accepted by ASF for inclusion in APR-UTIL,
I will assign copyright to ASF and license under ASF terms.
The current GPL satisfies MySQL licensing terms without
invoking any exceptions.
This code is untested, and probably mostly won't work.
It's a demo of concept.
*/
#if APU_HAVE_MYSQL
#include <ctype.h>
#include <stdlib.h>
#include <mysql/mysql.h>
#include <mysql/errmsg.h>
#include "apr_strings.h"
typedef MYSQL apr_dbd_t;
typedef MYSQL_STMT apr_dbd_prepared;
typedef struct {
int errnum;
} apr_dbd_transaction;
typedef struct {
int random;
MYSQL_RES *res;
MYSQL_STMT *statement;
MYSQL_BIND *bind;
} apr_dbd_results;
typedef struct {
MYSQL_ROW row;
apr_dbd_results *res;
} apr_dbd_row;
#define APR_DBD_INTERNAL
#include "apr_dbd.h"
static int dbd_mysql_select(apr_pool_t *pool, apr_dbd_t *sql,
apr_dbd_transaction *trans,
apr_dbd_results **results,
const char *query, int seek)
{
int sz;
int ret;
if (trans && trans->errnum) {
return trans->errnum;
}
ret = mysql_query(sql, query);
if (!ret) {
if (sz = mysql_field_count(sql), sz > 0) {
if (!*results) {
*results = apr_palloc(pool, sizeof(apr_dbd_results));
}
(*results)->random = seek;
(*results)->statement = NULL;
if (seek) {
(*results)->res = mysql_store_result(sql);
}
else {
(*results)->res = mysql_use_result(sql);
}
apr_pool_cleanup_register(pool, (*results)->res,
(void*)mysql_free_result,
apr_pool_cleanup_null);
}
}
if (trans) {
trans->errnum = ret;
}
return ret;
}
static int dbd_mysql_get_row(apr_pool_t *pool, apr_dbd_results *res,
apr_dbd_row **row, int rownum)
{
MYSQL_ROW r;
int ret = 0;
if (res->statement) {
if (res->random) {
if (rownum >= 0) {
mysql_stmt_data_seek(res->statement, (my_ulonglong)rownum);
}
}
ret = mysql_stmt_fetch(res->statement);
}
else {
if (res->random) {
if (rownum >= 0) {
mysql_data_seek(res->res, (my_ulonglong) rownum);
}
}
r = mysql_fetch_row(res->res);
if (r == NULL) {
ret = 1;
}
}
if (!ret) {
if (!*row) {
*row = apr_palloc(pool, sizeof(apr_dbd_row));
}
(*row)->row = r;
(*row)->res = res;
}
return (ret == 0) ? 0 : -1;
}
static const char *dbd_mysql_get_entry(const apr_dbd_row *row, int n)
{
MYSQL_BIND *bind;
if (row->res->statement) {
bind = &row->res->bind[n];
if ( mysql_stmt_fetch_column(row->res->statement, bind, n, 0) != 0 ) {
return NULL;
}
if ( *bind->is_null ) {
return NULL;
}
else {
return bind->buffer;
}
}
else {
return row->row[n];
}
}
static const char *dbd_mysql_error(apr_dbd_t *sql, int n)
{
return mysql_error(sql);
}
static int dbd_mysql_query(apr_dbd_t *sql, apr_dbd_transaction *trans,
int *nrows, const char *query)
{
int ret;
if (trans && trans->errnum) {
return trans->errnum;
}
ret = mysql_query(sql, query);
*nrows = mysql_affected_rows(sql);
if (trans) {
trans->errnum = ret;
}
return ret;
}
static const char *dbd_mysql_escape(apr_pool_t *pool, const char *arg,
apr_dbd_t *sql)
{
unsigned long len = strlen(arg);
char *ret = apr_palloc(pool, 2*len + 1);
mysql_real_escape_string(sql, ret, arg, len);
return ret;
}
static int dbd_mysql_prepare(apr_pool_t *pool, apr_dbd_t *sql,
const char *query, const char *label,
apr_dbd_prepared **statement)
{
*statement = mysql_stmt_init(sql);
apr_pool_cleanup_register(pool, *statement, (void*)mysql_stmt_close,
apr_pool_cleanup_null);
return mysql_stmt_prepare(*statement, query, strlen(query));
}
static int dbd_mysql_pquery(apr_pool_t *pool, apr_dbd_t *sql,
apr_dbd_transaction *trans, int *nrows,
apr_dbd_prepared *statement, ...)
{
MYSQL_BIND *bind;
char *arg;
int ret;
int nargs = 0;
int i;
va_list args;
my_bool is_null = FALSE;
if (trans && trans->errnum) {
return trans->errnum;
}
nargs = mysql_stmt_param_count(statement);
bind = apr_palloc(pool, nargs*sizeof(MYSQL_BIND));
va_start(args, statement);
for (i=0; i < nargs; ++i) {
arg = va_arg(args, char*);
bind[i].buffer_type = MYSQL_TYPE_STRING;
bind[i].buffer = arg;
bind[i].buffer_length = strlen(arg);
bind[i].length = &bind[i].buffer_length;
bind[i].is_null = &is_null;
bind[i].is_unsigned = 0;
}
va_end(args);
ret = mysql_stmt_bind_param(statement, bind);
if (ret != 0) {
*nrows = 0;
}
else {
ret = mysql_stmt_execute(statement);
*nrows = mysql_stmt_affected_rows(statement);
}
if (trans) {
trans->errnum = ret;
}
return ret;
}
static int dbd_mysql_pselect(apr_pool_t *pool, apr_dbd_t *sql,
apr_dbd_transaction *trans, apr_dbd_results **res,
apr_dbd_prepared *statement, int random, ...)
{
int i;
int nfields;
char *arg;
my_bool is_null = FALSE;
my_bool *is_nullr;
int ret;
const int FIELDSIZE = 255;
va_list args;
ulong *length;
char **data;
int nargs;
MYSQL_BIND *bind;
if (trans && trans->errnum) {
return trans->errnum;
}
nargs = mysql_stmt_param_count(statement);
bind = apr_palloc(pool, nargs*sizeof(MYSQL_BIND));
va_start(args, random);
for (i=0; i < nargs; ++i) {
arg = va_arg(args, char*);
bind[i].buffer_type = MYSQL_TYPE_STRING;
bind[i].buffer = arg;
bind[i].buffer_length = strlen(arg);
bind[i].length = &bind[i].buffer_length;
bind[i].is_null = &is_null;
bind[i].is_unsigned = 0;
}
va_end(args);
ret = mysql_stmt_bind_param(statement, bind);
if (ret == 0) {
ret = mysql_stmt_execute(statement);
if (!ret) {
if (!*res) {
*res = apr_pcalloc(pool, sizeof(apr_dbd_results));
}
(*res)->random = random;
(*res)->statement = statement;
(*res)->res = mysql_stmt_result_metadata(statement);
if (!*res) {
while (!mysql_stmt_fetch(statement));
return -1;
}
apr_pool_cleanup_register(pool, (*res)->res,
(void*)mysql_free_result, apr_pool_cleanup_null);
nfields = mysql_num_fields((*res)->res);
if (!(*res)->bind) {
(*res)->bind = apr_palloc(pool, nfields*sizeof(MYSQL_BIND));
length = apr_pcalloc(pool, nfields*sizeof(ulong));
data = apr_palloc(pool, nfields*sizeof(char*));
is_nullr = apr_pcalloc(pool, nfields*sizeof(my_bool));
length = apr_pcalloc(pool, nfields);
for ( i = 0; i < nfields; ++i ) {
(*res)->bind[i].buffer_type = MYSQL_TYPE_STRING;
(*res)->bind[i].buffer_length = FIELDSIZE;
(*res)->bind[i].length = &length[i];
data[i] = apr_palloc(pool, FIELDSIZE*sizeof(char));
(*res)->bind[i].buffer = data[i];
(*res)->bind[i].is_null = is_nullr+i;
}
}
ret = mysql_stmt_bind_result(statement, (*res)->bind);
if (!ret) {
ret = mysql_stmt_store_result(statement);
}
}
}
if (trans) {
trans->errnum = ret;
}
return ret;
}
/* Transactions don't work here (rollback statement reports success
* but rollback doesn't happen). Tried several variants based on
* SQL statements too. I expect it's down to mysql internals.
*/
static int dbd_mysql_transaction(apr_pool_t *pool, apr_dbd_t *handle,
apr_dbd_transaction **trans)
{
if (!*trans) {
*trans = apr_pcalloc(pool, sizeof(apr_dbd_transaction));
}
(*trans)->errnum = mysql_autocommit(handle, 0);
return (*trans)->errnum;
}
static int dbd_mysql_end_transaction(apr_dbd_t *handle,
apr_dbd_transaction *trans)
{
int ret = -1;
if (trans) {
if (trans->errnum) {
trans->errnum = 0;
ret = mysql_rollback(handle);
}
else {
ret = mysql_commit(handle);
}
}
ret |= mysql_autocommit(handle, 1);
return ret;
}
static apr_dbd_t *dbd_mysql_open(apr_pool_t *pool, const char *params)
{
static const char *const delims = " \r\n\t;|,";
const char *ptr;
int i;
const char *key;
size_t klen;
const char *value;
size_t vlen;
struct {
const char *field;
const char *value;
} fields[] = {
{"host", NULL},
{"user", NULL},
{"pass", NULL},
{"dbname", NULL},
{"port", NULL},
{"sock", NULL},
{NULL, NULL}
};
unsigned int port = 0;
MYSQL *sql = NULL;
sql = mysql_init(sql);
if ( sql == NULL ) {
return NULL;
}
for (ptr = strchr(params, '='); ptr; ptr = strchr(ptr, '=')) {
for (key = ptr-1; isspace(*key); --key);
klen = 0;
while (isalpha(*key)) {
--key;
++klen;
}
++key;
for (value = ptr+1; isspace(*value); ++value);
vlen = strcspn(value, delims);
for (i=0; fields[i].field != NULL; ++i) {
if (!strncasecmp(fields[i].field, key, klen)) {
fields[i].value = apr_pstrndup(pool, value, vlen);
break;
}
}
ptr = value+vlen;
}
if (fields[4].value != NULL) {
port = atoi(fields[4].value);
}
sql = mysql_real_connect(sql, fields[0].value, fields[1].value,
fields[2].value, fields[3].value,
port, fields[5].value, 0);
return sql;
}
static apr_status_t dbd_mysql_close(void *handle)
{
mysql_close(handle);
return APR_SUCCESS;
}
static apr_status_t dbd_mysql_check_conn(apr_pool_t *pool,
apr_dbd_t *handle)
{
return mysql_ping(handle) ? APR_EGENERAL : APR_SUCCESS;
}
static int dbd_mysql_select_db(apr_pool_t *pool, apr_dbd_t* handle,
const char* name)
{
return mysql_select_db(handle, name);
}
static void *dbd_mysql_native(apr_dbd_t *handle)
{
return handle;
}
static int dbd_mysql_num_cols(apr_dbd_results *res)
{
if (res->statement) {
return mysql_stmt_field_count(res->statement);
}
else {
return mysql_num_fields(res->res);
}
}
static int dbd_mysql_num_tuples(apr_dbd_results *res)
{
if (res->random) {
if (res->statement) {
return (int) mysql_stmt_num_rows(res->statement);
}
else {
return (int) mysql_num_rows(res->res);
}
}
else {
return -1;
}
}
APU_DECLARE_DATA const apr_dbd_driver_t apr_dbd_mysql_driver = {
"mysql",
dbd_mysql_native,
dbd_mysql_open,
dbd_mysql_check_conn,
dbd_mysql_close,
dbd_mysql_select_db,
dbd_mysql_transaction,
dbd_mysql_end_transaction,
dbd_mysql_query,
dbd_mysql_select,
dbd_mysql_num_cols,
dbd_mysql_num_tuples,
dbd_mysql_get_row,
dbd_mysql_get_entry,
dbd_mysql_error,
dbd_mysql_escape,
dbd_mysql_prepare,
dbd_mysql_pquery,
dbd_mysql_pselect,
};
#endif
/* Copyright (c) 2004, Nick Kew. All rights reserved.
If this is accepted by ASF for inclusion in APR-UTIL,
I will assign copyright to ASF and license under ASF terms.
Otherwise I will retain copyright and license under
terms of my choice.
*/
#if APU_HAVE_PGSQL
#include <ctype.h>
#include <stdlib.h>
#include <libpq-fe.h>
#include "apr_strings.h"
#define QUERY_MAX_ARGS 40
typedef PGconn apr_dbd_t;
typedef struct {
int errnum;
} apr_dbd_transaction;
typedef struct {
int random;
PGconn *handle;
PGresult *res;
size_t ntuples;
size_t sz;
size_t index;
} apr_dbd_results;
typedef struct {
int n;
apr_dbd_results *res;
} apr_dbd_row;
typedef struct apr_dbd_prepared {
const char *name;
int prepared;
} apr_dbd_prepared;
#define dbd_pgsql_is_success(x)
(((x)==PGRES_EMPTY_QUERY)||((x)==PGRES_COMMAND_OK)||((x)==PGRES_TUPLES_OK))
#define APR_DBD_INTERNAL
#include "apr_dbd.h"
static int dbd_pgsql_select(apr_pool_t *pool, apr_dbd_t *sql,
apr_dbd_transaction *trans,
apr_dbd_results **results,
const char *query, int seek) {
PGresult *res;
int ret;
if ( trans && trans->errnum ) {
return trans->errnum;
}
if (seek) { /* synchronous query */
res = PQexec(sql, 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 (trans) {
trans->errnum = ret;
}
return ret;
}
if (!*results) {
*results = apr_pcalloc(pool, sizeof(apr_dbd_results));
}
(*results)->res = res;
(*results)->ntuples = PQntuples(res);
(*results)->sz = PQnfields(res);
(*results)->random = seek;
apr_pool_cleanup_register(pool, res, (void*)PQclear,
apr_pool_cleanup_null);
}
else {
if (PQsendQuery(sql, query) == 0) {
if (trans) {
trans->errnum = 1;
}
return 1;
}
if ( !*results ) {
*results = apr_pcalloc(pool, sizeof(apr_dbd_results));
}
(*results)->random = seek;
(*results)->handle = sql;
}
return 0;
}
static int dbd_pgsql_get_row(apr_pool_t *pool, apr_dbd_results *res,
apr_dbd_row **rowp, int rownum)
{
apr_dbd_row *row = *rowp;
int sequential = ((rownum >= 0) && res->random) ? 0 : 1;
if ( !row ) {
row = apr_palloc(pool, sizeof(apr_dbd_row));
*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_kill(pool, res->res, (void*)PQclear);
PQclear(res->res);
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 *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);
}
static int dbd_pgsql_query(apr_dbd_t *sql, apr_dbd_transaction *trans,
int *nrows, const char *query)
{
PGresult *res;
int ret;
if (trans && trans->errnum) {
return trans->errnum;
}
res = PQexec(sql, 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 (trans) {
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, 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 **statement)
{
char *sqlcmd;
char *sqlptr;
size_t length;
size_t i;
const char *args[QUERY_MAX_ARGS];
size_t alen[QUERY_MAX_ARGS];
const char *e;
const char *q;
int nargs = 0;
int ret;
PGresult *res;
if (!*statement) {
*statement = apr_palloc(pool, sizeof(apr_dbd_prepared));
}
if (!label) {
/* don't really prepare; use in execParams instead */
(*statement)->prepared = 0;
(*statement)->name = apr_pstrdup(pool, query);
return 0;
}
(*statement)->name = apr_pstrdup(pool, label);
for ( q = strchr(query, '$'); q; q = strchr(e, '$') ) {
if ((q != query) && (q[-1] == '\\')) {
e = q + 1;
continue;
}
e = strchr(q, ';');
if (!e) {
break;
}
if (nargs >= QUERY_MAX_ARGS) {
return -1;
}
args[nargs] = q + 1;
alen[nargs] = e - (q+1);
++nargs;
}
length = 8 + strlen(label) + 1 + 2 + (2*nargs) + 4 + strlen(query) + 1;
sqlcmd = apr_palloc(pool, length);
sqlptr = sqlcmd;
memcpy(sqlptr, "PREPARE ", 8);
sqlptr += 8;
length = strlen(label);
memcpy(sqlptr, label, length);
sqlptr += length;
if (nargs > 0) {
memcpy(sqlptr, " (",2);
sqlptr += 2;
for (i=0; i<nargs; ++i) {
memcpy(sqlptr, args[i], alen[i]);
sqlptr += alen[i];
*sqlptr++ = ',';
}
sqlptr[-1] = ')';
}
memcpy(sqlptr, " AS ", 4);
sqlptr += 4;
i = 0;
for (q = query; *q; ++q) {
switch (*q) {
case '\\':
*sqlptr++ = *++q;
break;
case '$':
*sqlptr++ = *q++;
while (*q != ';') {
++q;
}
sqlptr += sprintf(sqlptr, "%d", ++i);
break;
default:
*sqlptr++ = *q;
break;
}
}
*sqlptr = 0;
res = PQexec(sql, 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,
apr_dbd_transaction *trans, int *nrows,
apr_dbd_prepared *statement, ...)
{
char *arg;
PGresult *res;
int ret;
int nargs = 0;
va_list args;
const char *values[QUERY_MAX_ARGS];
if (trans && trans->errnum) {
return trans->errnum;
}
va_start(args, statement);
while ( arg = va_arg(args, char*), arg ) {
if ( nargs >= QUERY_MAX_ARGS) {
va_end(args);
return -1;
}
values[nargs++] = apr_pstrdup(pool, arg) ;
}
va_end(args);
if (statement->prepared) {
res = PQexecPrepared(sql, statement->name, nargs, values, 0, 0, 0);
}
else {
res = PQexecParams(sql, statement->name, nargs, 0, values, 0, 0, 0);
}
if (res) {
ret = PQresultStatus(res);
if (dbd_pgsql_is_success(ret)) {
ret = 0;
}
PQclear(res);
}
else {
ret = PGRES_FATAL_ERROR;
}
if (trans) {
trans->errnum = ret;
}
return ret;
}
static int dbd_pgsql_pselect(apr_pool_t *pool, apr_dbd_t *sql,
apr_dbd_transaction *trans,
apr_dbd_results **results,
apr_dbd_prepared *statement,
int seek, ...)
{
const char *arg;
PGresult *res;
int rv;
int ret = 0;
int nargs = 0;
va_list args;
const char *values[QUERY_MAX_ARGS];
if (trans && trans->errnum) {
return trans->errnum;
}
va_start(args, seek);
while (arg = va_arg(args, const char*), arg) {
if ( nargs >= QUERY_MAX_ARGS) {
va_end(args);
return -1;
}
values[nargs++] = apr_pstrdup(pool, arg) ;
}
va_end(args);
if (seek) { /* synchronous query */
if (statement->prepared) {
res = PQexecPrepared(sql, statement->name, nargs, values, 0, 0, 0);
}
else {
res = PQexecParams(sql, 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 (trans) {
trans->errnum = ret;
}
return ret;
}
if (!*results) {
*results = apr_pcalloc(pool, sizeof(apr_dbd_results));
}
(*results)->res = res;
(*results)->ntuples = PQntuples(res);
(*results)->sz = PQnfields(res);
(*results)->random = seek;
apr_pool_cleanup_register(pool, res, (void*)PQclear,
apr_pool_cleanup_null);
}
else {
if (statement->prepared) {
rv = PQsendQueryPrepared(sql, statement->name, nargs, values,0,0,0);
}
else {
rv = PQsendQueryParams(sql, statement->name, nargs, 0,
values,0,0,0);
}
if (rv == 0) {
if (trans) {
trans->errnum = 1;
}
return 1;
}
if (!*results) {
*results = apr_pcalloc(pool, sizeof(apr_dbd_results));
}
(*results)->random = seek;
(*results)->handle = sql;
}
if (trans) {
trans->errnum = ret;
}
return ret;
}
static int dbd_pgsql_transaction(apr_pool_t *pool, apr_dbd_t *handle,
apr_dbd_transaction **trans)
{
int ret = 0;
PGresult *res = PQexec(handle, "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));
}
}
PQclear(res);
}
else {
ret = PGRES_FATAL_ERROR;
}
return ret;
}
static int dbd_pgsql_end_transaction(apr_dbd_t *handle,
apr_dbd_transaction *trans)
{
PGresult *res;
int ret = -1; /* no transaction is an error cond */
if (trans) {
if (trans->errnum) {
trans->errnum = 0;
res = PQexec(handle, "ROLLBACK");
}
else {
res = PQexec(handle, "COMMIT");
}
ret = PQresultStatus(res);
if (dbd_pgsql_is_success(ret)) {
ret = 0;
}
PQclear(res);
}
return ret;
}
static apr_dbd_t *dbd_pgsql_open(apr_pool_t *pool, const char *params)
{
return PQconnectdb(params);
}
static apr_status_t dbd_pgsql_close(void *handle)
{
PQfinish(handle);
return APR_SUCCESS;
}
static apr_status_t dbd_pgsql_check_conn(apr_pool_t *pool,
apr_dbd_t *handle)
{
if (PQstatus(handle) != CONNECTION_OK) {
PQreset(handle);
if (PQstatus(handle) != 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;
}
static int dbd_pgsql_num_cols(apr_dbd_results* res)
{
return res->sz;
}
static int dbd_pgsql_num_tuples(apr_dbd_results* res)
{
if (res->random) {
return res->ntuples;
}
else {
return -1;
}
}
APU_DECLARE_DATA const apr_dbd_driver_t apr_dbd_pgsql_driver = {
"pgsql",
dbd_pgsql_native,
dbd_pgsql_open,
dbd_pgsql_check_conn,
dbd_pgsql_close,
dbd_pgsql_select_db,
dbd_pgsql_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_pquery,
dbd_pgsql_pselect,
};
#endif
/* Copyright (c) 2004, Nick Kew. All rights reserved.
If this is accepted by ASF for inclusion in APR-UTIL,
I will assign copyright to ASF and license under ASF terms.
Otherwise I will retain copyright and license under
terms of my choice.
*/
#include "apu.h"
#include "apr_pools.h"
#include "apr_dbd.h"
#include <stdio.h>
#define TEST(msg,func) \
printf("======== %s ========\n", msg); \
rv = func(pool, sql, driver); \
if (rv != 0) { \
printf("Error in %s: rc=%d\n\n", msg, rv); \
} \
else { \
printf("%s test successful\n\n", msg); \
} \
fflush(stdout);
static int create_table(apr_pool_t* pool, apr_dbd_t* handle,
apr_dbd_driver_t* driver)
{
int rv = 0;
int nrows;
const char *statement = "CREATE TABLE apr_dbd_test ("
"col1 varchar(40) not null,"
"col2 varchar(40),"
"col3 integer)" ;
rv = apr_dbd_query(driver, handle, NULL, &nrows, statement);
return rv;
}
static int drop_table(apr_pool_t* pool, apr_dbd_t* handle,
apr_dbd_driver_t* driver)
{
int rv = 0;
int nrows;
const char *statement = "DROP TABLE apr_dbd_test" ;
rv = apr_dbd_query(driver, handle, NULL, &nrows, statement);
return rv;
}
static int insert_rows(apr_pool_t* pool, apr_dbd_t* handle,
apr_dbd_driver_t* driver)
{
int i;
int rv = 0;
int nrows;
int nerrors = 0;
const char *statement =
"INSERT into apr_dbd_test (col1) values ('foo');"
"INSERT into apr_dbd_test values ('wibble', 'other', 5);"
"INSERT into apr_dbd_test values ('wibble', 'nothing', 5);"
"INSERT into apr_dbd_test values ('qwerty', 'foo', 0);"
"INSERT into apr_dbd_test values ('asdfgh', 'bar', 1);"
;
rv = apr_dbd_query(driver, handle, NULL, &nrows, statement);
if (rv) {
const char* stmt[] = {
"INSERT into apr_dbd_test (col1) values ('foo');",
"INSERT into apr_dbd_test values ('wibble', 'other', 5);",
"INSERT into apr_dbd_test values ('wibble', 'nothing', 5);",
"INSERT into apr_dbd_test values ('qwerty', 'foo', 0);",
"INSERT into apr_dbd_test values ('asdfgh', 'bar', 1);",
NULL
};
printf("Compound insert failed; trying statements one-by-one\n") ;
for (i=0; stmt[i] != NULL; ++i) {
statement = stmt[i];
rv = apr_dbd_query(driver, handle, NULL, &nrows, statement);
if (rv) {
nerrors++;
}
}
if (nerrors) {
printf("%d single inserts failed too.\n", nerrors) ;
}
}
return rv;
}
static int invalid_op(apr_pool_t* pool, apr_dbd_t* handle,
apr_dbd_driver_t* driver)
{
int rv = 0;
int nrows;
const char *statement = "INSERT into apr_dbd_test1 (col2) values ('foo')" ;
rv = apr_dbd_query(driver, handle, NULL, &nrows, statement);
printf("invalid op returned %d (should be nonzero). Error msg follows\n",
rv);
printf("'%s'\n", apr_dbd_error(driver, handle, rv));
statement = "INSERT into apr_dbd_test (col1, col2) values ('bar', 'foo')" ;
rv = apr_dbd_query(driver, handle, NULL, &nrows, statement);
printf("valid op returned %d (should be zero; error shouldn't affect
subsequent ops)\n", rv);
return rv;
}
static int select_sequential(apr_pool_t* pool, apr_dbd_t* handle,
apr_dbd_driver_t* driver)
{
int rv = 0;
int i = 0;
int n;
const char* entry;
const char* statement = "SELECT * FROM apr_dbd_test ORDER BY col1, col2";
apr_dbd_results *res = NULL;
apr_dbd_row *row = NULL;
rv = apr_dbd_select(driver,pool,handle,NULL,&res,statement,0);
if (rv) {
printf("Select failed: %s", apr_dbd_error(driver, handle, rv));
return rv;
}
for (rv = apr_dbd_get_row(driver, pool, res, &row, -1);
rv == 0;
rv = apr_dbd_get_row(driver, pool, res, &row, -1)) {
printf("ROW %d: ", i++) ;
for (n = 0; n < apr_dbd_num_cols(driver, res); ++n) {
entry = apr_dbd_get_entry(driver, row, n);
if (entry == NULL) {
printf("(null) ") ;
}
else {
printf("%s ", entry);
}
}
fputs("\n", stdout);
}
return (rv == -1) ? 0 : 1;
}
static int select_random(apr_pool_t* pool, apr_dbd_t* handle,
apr_dbd_driver_t* driver)
{
int rv = 0;
int n;
const char* entry;
const char* statement = "SELECT * FROM apr_dbd_test ORDER BY col1, col2";
apr_dbd_results *res = NULL;
apr_dbd_row *row = NULL;
rv = apr_dbd_select(driver,pool,handle,NULL,&res,statement,1);
if (rv) {
printf("Select failed: %s", apr_dbd_error(driver, handle, rv));
return rv;
}
rv = apr_dbd_get_row(driver, pool, res, &row, 5) ;
if (rv) {
printf("get_row failed: %s", apr_dbd_error(driver, handle, rv));
return rv;
}
printf("ROW 5: ");
for (n = 0; n < apr_dbd_num_cols(driver, res); ++n) {
entry = apr_dbd_get_entry(driver, row, n);
if (entry == NULL) {
printf("(null) ") ;
}
else {
printf("%s ", entry);
}
}
fputs("\n", stdout);
rv = apr_dbd_get_row(driver, pool, res, &row, 1) ;
if (rv) {
printf("get_row failed: %s", apr_dbd_error(driver, handle, rv));
return rv;
}
printf("ROW 1: ");
for (n = 0; n < apr_dbd_num_cols(driver, res); ++n) {
entry = apr_dbd_get_entry(driver, row, n);
if (entry == NULL) {
printf("(null) ") ;
}
else {
printf("%s ", entry);
}
}
fputs("\n", stdout);
rv = apr_dbd_get_row(driver, pool, res, &row, 11) ;
if (rv != -1) {
printf("Oops! get_row out of range but thinks it succeeded!\n%s\n",
apr_dbd_error(driver, handle, rv));
return -1;
}
rv = 0;
return rv;
}
static int test_transactions(apr_pool_t* pool, apr_dbd_t* handle,
apr_dbd_driver_t* driver)
{
int rv = 0;
int nrows;
apr_dbd_transaction* trans = NULL;
const char* statement;
/* trans 1 - error out early */
printf("Transaction 1\n");
rv = apr_dbd_transaction_start(driver, pool, handle, &trans);
if (rv) {
printf("Start transaction failed!\n%s\n",
apr_dbd_error(driver, handle, rv));
return rv;
}
statement = "UPDATE apr_dbd_test SET col2 = 'failed'";
rv = apr_dbd_query(driver, handle, trans, &nrows, statement);
if (rv) {
printf("Update failed: '%s'\n", apr_dbd_error(driver, handle, rv));
apr_dbd_transaction_end(driver, handle, trans);
return rv;
}
printf("%d rows updated\n", nrows);
statement = "INSERT INTO apr_dbd_test1 (col3) values (3)";
rv = apr_dbd_query(driver, handle, trans, &nrows, statement);
if (!rv) {
printf("Oops, invalid op succeeded but shouldn't!\n");
}
statement = "INSERT INTO apr_dbd_test values ('zzz', 'aaa', 3)";
rv = apr_dbd_query(driver, handle, trans, &nrows, statement);
printf("Valid insert returned %d. Should be nonzero (fail) because
transaction is bad\n", rv) ;
rv = apr_dbd_transaction_end(driver, handle, trans);
if (rv) {
printf("End transaction failed!\n%s\n",
apr_dbd_error(driver, handle, rv));
return rv;
}
printf("Transaction ended (should be rollback) - viewing table\n"
"A column of \"failed\" indicates transaction failed (no
rollback)\n");
select_sequential(pool, handle, driver);
/* trans 2 - complete successfully */
printf("Transaction 2\n");
rv = apr_dbd_transaction_start(driver, pool, handle, &trans);
if (rv) {
printf("Start transaction failed!\n%s\n",
apr_dbd_error(driver, handle, rv));
return rv;
}
statement = "UPDATE apr_dbd_test SET col2 = 'success'";
rv = apr_dbd_query(driver, handle, trans, &nrows, statement);
if (rv) {
printf("Update failed: '%s'\n", apr_dbd_error(driver, handle, rv));
apr_dbd_transaction_end(driver, handle, trans);
return rv;
}
printf("%d rows updated\n", nrows);
statement = "INSERT INTO apr_dbd_test values ('aaa', 'zzz', 3)";
rv = apr_dbd_query(driver, handle, trans, &nrows, statement);
printf("Valid insert returned %d. Should be zero (OK)\n", rv) ;
rv = apr_dbd_transaction_end(driver, handle, trans);
if (rv) {
printf("End transaction failed!\n%s\n",
apr_dbd_error(driver, handle, rv));
return rv;
}
printf("Transaction ended (should be commit) - viewing table\n");
select_sequential(pool, handle, driver);
return rv;
}
static int test_pselect(apr_pool_t* pool, apr_dbd_t* handle,
apr_dbd_driver_t* driver)
{
int rv = 0;
int i, n;
const char *query = NULL;
const char *label = "lowvalues";
apr_dbd_prepared* statement = NULL;
apr_dbd_results* res = NULL;
apr_dbd_row* row = NULL;
const char *entry = NULL;
struct {
const char* name;
const char* query;
} queries[] = {
{ "mysql", "SELECT * FROM apr_dbd_test WHERE col3 <= ? or col1 = 'bar'"
} ,
{ "pgsql", "SELECT * FROM apr_dbd_test WHERE col3 <= $integer; or col1
= 'bar'" } ,
{ NULL, NULL }
};
for (i = 0; queries[i].name != NULL; ++i) {
if ( !strcmp(queries[i].name, apr_dbd_name(driver))) {
query = queries[i].query;
break;
}
}
if (!query) {
return -1;
}
rv = apr_dbd_prepare(driver, pool, handle, query, label, &statement);
if (rv) {
printf("Prepare statement failed!\n%s\n",
apr_dbd_error(driver, handle, rv));
return rv;
}
rv = driver->pselect(pool, handle, NULL, &res, statement, 1, "3", NULL);
if (rv) {
printf("Exec of prepared statement failed!\n%s\n",
apr_dbd_error(driver, handle, rv));
return rv;
}
i = 0;
printf("Selecting rows where col3 <= 3 and bar row where it's
unset.\nShould show four rows.\n");
for (rv = apr_dbd_get_row(driver, pool, res, &row, -1);
rv == 0;
rv = apr_dbd_get_row(driver, pool, res, &row, -1)) {
printf("ROW %d: ", i++) ;
for (n = 0; n < apr_dbd_num_cols(driver, res); ++n) {
entry = apr_dbd_get_entry(driver, row, n);
if (entry == NULL) {
printf("(null) ") ;
}
else {
printf("%s ", entry);
}
}
fputs("\n", stdout);
}
return (rv == -1) ? 0 : 1;
}
static int test_pquery(apr_pool_t* pool, apr_dbd_t* handle,
apr_dbd_driver_t* driver)
{
int i;
int rv = 0;
const char *query;
apr_dbd_prepared *statement;
const char *label = "pquery";
int nrows;
struct {
const char* name;
const char* query;
} queries[] = {
{ "mysql", "INSERT INTO apr_dbd_test VALUES (? , ? , ?)" } ,
{ "pgsql", "INSERT INTO apr_dbd_test VALUES ($varchar;, $varchar;,
$integer;)" } ,
{ NULL, NULL }
};
for (i = 0; queries[i].name != NULL; ++i) {
if ( !strcmp(queries[i].name, apr_dbd_name(driver))) {
query = queries[i].query;
break;
}
}
if (!query) {
return -1;
}
rv = apr_dbd_prepare(driver, pool, handle, query, label, &statement);
if (rv) {
printf("Prepare statement failed!\n%s\n",
apr_dbd_error(driver, handle, rv));
return rv;
}
rv = driver->pquery(pool, handle, NULL, &nrows, statement,
"prepared", "insert", "2", NULL);
if (rv) {
printf("Exec of prepared statement failed!\n%s\n",
apr_dbd_error(driver, handle, rv));
return rv;
}
printf("Showing table (should now contain row \"prepared insert 2\")\n");
select_sequential(pool, handle, driver);
return rv;
}
int main(int argc, char** argv)
{
apr_pool_t *pool;
const char *name;
const char *params;
apr_dbd_t *sql = NULL;
apr_dbd_driver_t *driver = NULL;
int rv;
apr_initialize();
apr_pool_create(&pool, NULL);
if (argc >= 2 && argc <= 3) {
name = argv[1];
params = ( argc == 3 ) ? argv[2] : "";
rv = apr_dbd_open(pool, pool, name, params, &sql, &driver);
switch (rv) {
case APR_SUCCESS:
printf("Opened %s[%s] OK\n", name, params);
break;
case APR_EGENERAL:
printf("Failed to open %s[%s]\n", name, params);
break;
case APR_ENOTIMPL:
printf("No driver for %s available\n", name);
break;
default: /* it's a bug if none of the above happen */
printf("Internal error opening %s[%s]\n", name, params);
break;
}
if (rv == APR_SUCCESS) {
TEST("create table", create_table);
TEST("insert rows", insert_rows);
TEST("invalid op", invalid_op);
TEST("select sequential", select_sequential);
TEST("select random", select_random);
TEST("transactions", test_transactions);
TEST("prepared select", test_pselect);
TEST("prepared query", test_pquery);
TEST("drop table", drop_table);
}
}
else {
fprintf(stderr, "Usage: %s driver-name [params]\n", argv[0]);
}
apr_pool_destroy(pool);
apr_terminate();
return 0;
}