/** * 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; }