/**
* db.c
*
* Copyright (C) 2003 - 2008 Bojan Smojver, Rexursive
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see .
*
*/
#include "private.h"
/* various get/set functions */
apr_pool_t *rxv_spin_db_pool(rxv_spin_db_t *db){
return db?db->pool:NULL;
}
char *rxv_spin_db_cinfo(rxv_spin_db_t *db){
return db?db->cp->cinfo:NULL;
}
const apr_dbd_driver_t *rxv_spin_db_driver(rxv_spin_db_t *db){
return db?db->cp->driver:NULL;
}
apr_dbd_t *rxv_spin_db_handle(rxv_spin_db_t *db){
return db?db->handle:NULL;
}
apr_dbd_transaction_t *rxv_spin_db_txn(rxv_spin_db_txn_t *txn){
return txn?txn->txn:NULL;
}
/* connection functions */
static apr_status_t pool_clean(void *data){
rxv_spin_pool_t *cp=data;
apr_hash_set(cp->priv->conns,cp->cinfo,APR_HASH_KEY_STRING,NULL);
APR_RING_REMOVE(cp,link);
return APR_SUCCESS;
}
static apr_status_t db_close(void *data){
rxv_spin_db_t *db=data;
db->pool=NULL; /* so that destructor does not destroy the pool again */
return apr_dbd_close(db->cp->driver,db->handle);
}
static apr_status_t db_create(void **rsc,void *parms,apr_pool_t *pool){
apr_status_t rv;
rxv_spin_db_t *db;
rxv_spin_pool_t *cp=parms;
db=calloc(1,sizeof(*db)); /* must be non-pool space */
#if APR_HAS_THREADS
if(cp->priv->mutex)
apr_pool_create(&db->pool,pool);
else
#endif
db->pool=cp->pool;
if((rv=apr_dbd_open(cp->driver,db->pool,cp->parms,&db->handle))!=APR_SUCCESS){
if(db->pool!=cp->pool) apr_pool_destroy(db->pool);
free(db);
return rv;
}
/* For connections where cp->pool is the same as db->pool, registering this
* here would cause it to run last, _after_ db structure has been
* deallocated. We shall do this in rxv_spin_db_open() instead. */
if(db->pool!=cp->pool)
apr_pool_cleanup_register(db->pool,db,db_close,apr_pool_cleanup_null);
db->cp=cp;
*rsc=db;
return APR_SUCCESS;
}
static apr_status_t db_destroy(void *rsc,void *parms,apr_pool_t *pool){
rxv_spin_db_t *db=rsc;
if(db->pool) apr_pool_destroy(db->pool);
free(db); /* non-pool space */
return APR_SUCCESS;
}
static apr_status_t db_clean(void *data){
apr_status_t rv;
rxv_spin_db_t *db=data;
if(
#if APR_HAS_THREADS
db->cp->list ||
#endif
db->cp->conn){
#if APR_HAS_THREADS
if(db->cp->priv->mutex &&
(rv=apr_reslist_release(db->cp->list,db))!=APR_SUCCESS)
return rv;
#endif
} else{
if((rv=db_destroy(db,db->cp,NULL))!=APR_SUCCESS)
return rv;
}
return APR_SUCCESS;
}
#define CONN_TMOUT (60*1000000)
rxv_spin_db_t *rxv_spin_db_open(rxv_spin_ctx_t *ctx,const char *conninfo){
rxv_spin_private_t *priv;
rxv_spin_pool_t *cp;
rxv_spin_db_t *db=NULL;
char *p,*dname;
apr_pool_t *lpool;
if(!(ctx && conninfo))
return NULL;
priv=ctx->priv;
/* try to reuse connection from the pool */
if(ctx->conf->connpool){
rxv_spin_lock(priv);
if((cp=apr_hash_get(priv->conns,conninfo,APR_HASH_KEY_STRING))){
cp->atime=APR_INT64_MIN; /* prevent removal after lock is removed */
rxv_spin_unlock(priv);
#if APR_HAS_THREADS
if(cp->priv->mutex){
if(apr_reslist_acquire(cp->list,(void **)&db)==APR_SUCCESS){
if(rxv_spin_db_status(ctx,db)==APR_SUCCESS)
apr_pool_cleanup_register(ctx->r->pool,
db,db_clean,apr_pool_cleanup_null);
else
db=NULL;
} else{
db=NULL;
}
} else{
#endif
if((db=cp->conn)){
if(rxv_spin_db_status(ctx,db)==APR_SUCCESS)
apr_pool_cleanup_register(ctx->r->pool,
db,db_clean,apr_pool_cleanup_null);
else
return NULL;
} else{
apr_pool_destroy(cp->pool); /* expire this connection pool */
return NULL;
}
#if APR_HAS_THREADS
}
#endif
rxv_spin_lock(priv);
if(ctx->r->request_time>cp->atime) cp->atime=ctx->r->request_time;
APR_RING_REMOVE(cp,link);
APR_RING_INSERT_TAIL(&priv->list,cp,rxv_spin_pool,link);
rxv_spin_unlock(priv);
return db;
}
}
/* we have to have driver:conn_string format */
if(!((p=strchr(conninfo,':')) && *(p+1))){
if(ctx->conf->connpool) rxv_spin_unlock(priv);
return NULL;
}
/* connection specific memory pool */
if(ctx->conf->connpool)
apr_pool_create(&lpool,priv->pool);
else
lpool=ctx->r->pool;
dname=apr_pstrmemdup(lpool,conninfo,p-conninfo);
/* connection not found, we'll set one up */
cp=apr_pcalloc(lpool,sizeof(*cp));
/* fetch database specific driver */
if(apr_dbd_get_driver(priv->pool,dname,&cp->driver)!=APR_SUCCESS){
if(ctx->conf->connpool){
apr_pool_destroy(lpool);
rxv_spin_unlock(priv);
}
return NULL;
}
/* we have to copy into the connection pool now */
cp->pool=lpool;
cp->cinfo=apr_pstrdup(lpool,conninfo);
cp->parms=cp->cinfo+(p-conninfo)+1;
cp->priv=priv;
if(ctx->conf->connpool){
#if APR_HAS_THREADS
if(cp->priv->mutex){
if(apr_reslist_create(&cp->list,
0,0,priv->count,CONN_TMOUT,
db_create,db_destroy,cp,lpool)!=APR_SUCCESS ||
apr_reslist_acquire(cp->list,(void **)&db)!=APR_SUCCESS){
apr_pool_destroy(lpool);
rxv_spin_unlock(priv);
return NULL;
}
} else{
#endif
if(db_create((void **)&db,cp,lpool)==APR_SUCCESS){
cp->conn=db;
} else{
apr_pool_destroy(lpool);
rxv_spin_unlock(priv);
return NULL;
}
#if APR_HAS_THREADS
}
#endif
cp->atime=ctx->r->request_time;
APR_RING_INSERT_TAIL(&priv->list,cp,rxv_spin_pool,link);
apr_hash_set(priv->conns,cp->cinfo,APR_HASH_KEY_STRING,cp);
apr_pool_cleanup_register(lpool,cp,pool_clean,apr_pool_cleanup_null);
rxv_spin_unlock(priv);
} else if(db_create((void *)&db,cp,lpool)!=APR_SUCCESS){
return NULL;
}
apr_pool_cleanup_register(ctx->r->pool,db,db_clean,apr_pool_cleanup_null);
/* For connections where cp->pool is the same as db->pool, we must register
* this last, so it runs _before_ db structure is deallocated. */
if(db->pool==cp->pool)
apr_pool_cleanup_register(db->pool,db,db_close,apr_pool_cleanup_null);
return db;
}
apr_status_t rxv_spin_db_close(rxv_spin_ctx_t *ctx,rxv_spin_db_t *db){
return apr_pool_cleanup_run(ctx->r->pool,db,db_clean);
}
apr_status_t rxv_spin_db_status(rxv_spin_ctx_t *ctx,rxv_spin_db_t *db){
apr_status_t rv;
if(!(ctx && db))
return APR_EGENERAL;
if((rv=apr_dbd_check_conn(db->cp->driver,
ctx->r->pool,db->handle))!=APR_SUCCESS){
apr_pool_cleanup_kill(ctx->r->pool,db,db_clean);
apr_pool_cleanup_kill(db->pool,db,db_close);
db_close(db);
if(
#if APR_HAS_THREADS
db->cp->list ||
#endif
db->cp->conn){
#if APR_HAS_THREADS
if(db->cp->priv->mutex){
apr_reslist_invalidate(db->cp->list,db);
} else{
#endif
apr_pool_destroy(db->cp->pool); /* expire this connection pool */
#if APR_HAS_THREADS
}
#endif
} else{
db_destroy(db,db->cp,NULL);
}
return rv;
}
return APR_SUCCESS;
}
/*
* WARNING: BIG TIME CHEATING!
*/
/* the fake apr_dbd_get_name function */
#ifndef HAVE_APR_DBD_GET_NAME
static const char *get_name(const apr_dbd_driver_t *driver,apr_pool_t *pool,
apr_dbd_results_t *res,int col){
#if defined (HAVE_LIBPQ) && defined (HAVE_LIBPQ_FE_H)
struct apr_dbd_pgsql_results_t{
int random;
PGconn *handle;
PGresult *res;
size_t ntuples;
size_t sz;
size_t index;
} *pgres;
#endif
#if defined (HAVE_LIBMYSQLCLIENT_R) && defined (HAVE_MYSQL_H)
struct apr_dbd_mysql_results_t{
int random;
MYSQL_RES *res;
MYSQL_STMT *statement;
MYSQL_BIND *bind;
} *myres;
#endif
#if defined (HAVE_LIBSQLITE) && defined (HAVE_SQLITE_H)
struct apr_dbd_sqlite2_results_t{
int random;
sqlite *handle;
char **res;
size_t ntuples;
size_t sz;
size_t index;
} *s2res;
#endif
#if defined (HAVE_LIBSQLITE3) && defined (HAVE_SQLITE3_H)
typedef struct{
char *name;
char *value;
int size;
int type;
} apr_dbd_sqlite3_column_t;
typedef struct{
apr_dbd_results_t *res;
apr_dbd_sqlite3_column_t **columns;
apr_dbd_row_t *next_row;
int columnCount;
int rownum;
} apr_dbd_sqlite3_row_t;
struct apr_dbd_sqlite3_results_t{
int random;
sqlite3 *handle;
sqlite3_stmt *stmt;
apr_dbd_sqlite3_row_t *next_row;
size_t sz;
int tuples;
char **col_names;
} *s3res;
#endif
const char *dname=apr_dbd_name(driver);
if(!strcmp(dname,"pgsql")){
#if defined (HAVE_LIBPQ) && defined (HAVE_LIBPQ_FE_H)
pgres=(struct apr_dbd_pgsql_results_t *)res;
return (pgres->res?PQfname(pgres->res,col):NULL);
#endif
} else if(!strcmp(dname,"mysql")){
#if defined (HAVE_LIBMYSQLCLIENT_R) && defined (HAVE_MYSQL_H)
myres=(struct apr_dbd_mysql_results_t *)res;
if((col<0) || (col>=mysql_num_fields(myres->res)))
return NULL;
return mysql_fetch_fields(myres->res)[col].name;
#endif
} else if(!strcmp(dname,"sqlite2")){
#if defined (HAVE_LIBSQLITE) && defined (HAVE_SQLITE_H)
s2res=(struct apr_dbd_sqlite2_results_t *)res;
if((col<0) || (col>=s2res->sz))
return NULL;
return s2res->res[col];
#endif
} else if(!strcmp(dname,"sqlite3")){
#if defined (HAVE_LIBSQLITE3) && defined (HAVE_SQLITE3_H)
s3res=(struct apr_dbd_sqlite3_results_t *)res;
if((col<0) || (col>=s3res->sz))
return NULL;
return s3res->next_row->columns[col]->name;
#endif
}
return apr_psprintf(pool,"column%d",col);
}
#endif
/* result conversion functions */
static rxv_spin_data_t *db_data(apr_pool_t *pool,apr_pool_t *spool,
rxv_spin_db_t *db,apr_dbd_results_t *dbdres){
rxv_spin_data_t *result,*single;
apr_dbd_row_t *row=NULL;
int i,cols;
const char *entry,**cnames;
apr_array_header_t **as;
if(!(pool && spool && dbdres))
return NULL;
/* get one row in order to fetch number and names of columns */
if(apr_dbd_get_row(db->cp->driver,spool,dbdres,&row,-1) ||
(cols=apr_dbd_num_cols(db->cp->driver,dbdres))<=0)
return NULL;
cnames=apr_palloc(spool,sizeof(*cnames)*cols);
for(i=0;icp->driver,dbdres,i);
#else
cnames[i]=get_name(db->cp->driver,spool,dbdres,i);
#endif
if(!cnames[i])
return NULL;
cnames[i]=apr_pstrdup(pool,cnames[i]);
}
result=apr_pcalloc(pool,sizeof(*result));
result->size=RXV_SPIN_ROWS;
result->cols=apr_hash_make(pool);
as=apr_pcalloc(spool,sizeof(*as)*cols);
for(i=0;icols,cnames[i],APR_HASH_KEY_STRING,as[i]);
}
do{
for(i=0;icp->driver,row,i))){
single=apr_array_push(as[i]);
single->data=apr_pstrdup(pool,entry);
single->size=strlen(entry);
}
}
} while(!apr_dbd_get_row(db->cp->driver,spool,dbdres,&row,-1));
return result;
}
rxv_spin_data_t *rxv_spin_db_data(apr_pool_t *pool,rxv_spin_db_t *db,
apr_dbd_results_t *dbdres){
return db_data(pool,pool,db,dbdres);
}
/* query related functions */
rxv_spin_data_t *rxv_spin_db_select(apr_pool_t *pool,rxv_spin_db_t *db,
const char *query){
apr_dbd_results_t *dbdres=NULL;
rxv_spin_data_t *res;
apr_pool_t *spool;
if(!(pool && db && query))
return NULL;
apr_pool_create(&spool,pool);
if(apr_dbd_select(db->cp->driver,spool,db->handle,&dbdres,query,0)){
apr_pool_destroy(spool);
return NULL;
}
res=db_data(pool,spool,db,dbdres);
apr_pool_destroy(spool);
return res;
}
int rxv_spin_db_query(apr_pool_t *pool,rxv_spin_db_t *db,const char *query){
int nrows=-1;
if(!(pool && db && query))
return -1;
if(apr_dbd_query(db->cp->driver,db->handle,&nrows,query))
return -1;
return nrows;
}
/* point here if there is prepared statement support, but prepare failed */
static const apr_dbd_prepared_t *badstmt=(apr_dbd_prepared_t *)"";
/* prepared statement related functions */
static apr_dbd_prepared_t *db_prepare(apr_pool_t *pool,rxv_spin_db_t *db,
const char *query){
apr_dbd_prepared_t *stmt=NULL;
char *q,*ascdig;
if(!(pool && db && query))
return NULL;
/* try to reuse statement from the connection */
if(db->stmts){
if((stmt=apr_hash_get(db->stmts,query,APR_HASH_KEY_STRING)))
return stmt==badstmt?NULL:stmt;
} else{
db->stmts=apr_hash_make(db->pool);
}
if(!(ascdig=rxv_spin_hash(pool,query)))
return NULL;
q=apr_pstrdup(db->pool,query);
if(apr_dbd_prepare(db->cp->driver,db->pool,
db->handle,query,ascdig,&stmt)==APR_SUCCESS)
apr_hash_set(db->stmts,q,APR_HASH_KEY_STRING,stmt);
else
apr_hash_set(db->stmts,q,APR_HASH_KEY_STRING,badstmt);
return stmt;
}
rxv_spin_data_t *rxv_spin_db_pselect(apr_pool_t *pool,rxv_spin_db_t *db,
const char *query,...){
apr_dbd_prepared_t *stmt=NULL;
apr_dbd_results_t *dbdres=NULL;
rxv_spin_data_t *res;
apr_pool_t *spool;
int nargs=0;
const char **args,**p;
va_list ap;
/* find the number of arguments */
va_start(ap,query);
for(;va_arg(ap,const char*);nargs++)
;
va_end(ap);
/* copy arguments to an array */
args=apr_pcalloc(pool,sizeof(*args)*(nargs+1));
va_start(ap,query);
for(p=args;(*p=va_arg(ap,const char*));p++)
;
va_end(ap);
apr_pool_create(&spool,pool);
if(!(stmt=db_prepare(spool,db,query))){
apr_pool_destroy(spool);
return NULL;
}
if(apr_dbd_pselect(db->cp->driver,spool,
db->handle,&dbdres,stmt,0,nargs,args)){
apr_pool_destroy(spool);
return NULL;
}
res=db_data(pool,spool,db,dbdres);
apr_pool_destroy(spool);
return res;
}
int rxv_spin_db_pquery(apr_pool_t *pool,rxv_spin_db_t *db,
const char *query,...){
apr_dbd_prepared_t *stmt=NULL;
int nrows=-1,nargs=0;
const char **args,**p;
va_list ap;
/* find the number of arguments */
va_start(ap,query);
for(;va_arg(ap,const char*);nargs++)
;
va_end(ap);
/* copy arguments to an array */
args=apr_pcalloc(pool,sizeof(*args)*(nargs+1));
va_start(ap,query);
for(p=args;(*p=va_arg(ap,const char*));p++)
;
va_end(ap);
if(!(stmt=db_prepare(pool,db,query)))
return -1;
if(apr_dbd_pquery(db->cp->driver,pool,db->handle,&nrows,stmt,nargs,args))
return -1;
return nrows;
}
rxv_spin_db_txn_t *rxv_spin_db_start(apr_pool_t *pool,rxv_spin_db_t *db){
rxv_spin_db_txn_t *txn;
if(!(pool && db))
return NULL;
txn=apr_pcalloc(pool,sizeof(*txn));
if(!apr_dbd_transaction_start(db->cp->driver,pool,db->handle,&txn->txn)){
txn->pool=pool;
txn->db=db;
return txn;
}
return NULL;
}
apr_status_t rxv_spin_db_end(rxv_spin_db_txn_t *txn){
if(!txn)
return APR_EGENERAL;
if(!apr_dbd_transaction_end(txn->db->cp->driver,txn->pool,txn->txn))
return APR_SUCCESS;
return APR_EGENERAL;
}