/** * db.c * * Copyright (C) 2003 - 2005 Bojan Smojver, Rexursive * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licence as published by the Free * Software Foundation; either version 2 of the Licence, 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 Licence for * more details. * * You should have received a copy of the GNU General Public Licence along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA * * In addition, as two special exceptions, Bojan Smojver, Rexursive, gives * permission to: * * 1. Link, both statically and dynamically, the code of this program with * Apache HTTP Server, Apache Portable Runtime and Apache Portable Runtime * Utility Library from Apache Software Foundation (or with modified versions * of the above, that use the same licence - Apache Software Licence 1.1, 2.0 * or any later version), and distribute linked combinations including the * program and the above software from Apache Software Foundation. You must * obey the GNU General Public Licence in all respects for all of the code * used other than Apache HTTP Server, Apache Portable Runtime and Apache * Portable Runtime Utility Library. * * 2. Dynamically link any shared library with this program at run-time, * through the interface of SpinApplication/SpinAppEntry or LoadModule * run-time configuration directives of Apache HTTP Server, as provided by * this program or Apache HTTP Server itself, regardless of licensing terms of * those shared libraries. You must obey the GNU General Public Licence in all * respects for all of the code used other than that of those shared * libraries. * * If you modify this file, you may extend these exceptions to your version of * the file, but you are not obligated to do so. If you do not wish to do so, * delete one or both of these exception statements from your version. * */ #include "private.h" /* connection and info functions */ /* * The following function, conninfo_parse has been modified from PostgreSQL * 7.4.1 software distribution. The copyright notice of it is below: * * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * Permission to use, copy, modify, and distribute this software and its * documentation for any purpose, without fee, and without a written agreement * is hereby granted, provided that the above copyright notice and this * paragraph and the following two paragraphs appear in all copies. * * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS * DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. */ #ifdef HAVE_LIBMYSQLCLIENT static apr_status_t conninfo_parse(const char *conninfo,apr_pool_t *pool, char **host,char **user,char **password, char **dbname,char **usocket, unsigned int *port){ char *pname,*pval,*buf,*cp,*cp2,*option, *options[]={"host","port","dbname","user","password",NULL}; /* Need a modifiable copy of the input string */ if(!(buf=apr_pstrdup(pool,conninfo))) return APR_ENOMEM; /* Make sure we're clean */ *host=*user=*password=*dbname=*usocket=NULL; *port=0; cp=buf; while(*cp){ /* Skip blanks before the parameter name */ if (isspace((unsigned char)*cp)){ cp++; continue; } /* Get the parameter name */ pname=cp; while(*cp){ if (*cp=='=') break; if(isspace((unsigned char)*cp)){ *cp++='\0'; while (*cp){ if (!isspace((unsigned char)*cp)) break; cp++; } break; } cp++; } /* Check that there is a following '=' */ if (*cp!='=') return APR_EGENERAL; *cp++='\0'; /* Skip blanks after the '=' */ while(*cp){ if (!isspace((unsigned char)*cp)) break; cp++; } /* Get the parameter value */ pval=cp; if (*cp!='\''){ cp2=pval; while(*cp){ if (isspace((unsigned char)*cp)){ *cp++='\0'; break; } if (*cp=='\\'){ cp++; if (*cp!='\0') *cp2++=*cp++; }else *cp2++=*cp++; } *cp2='\0'; } else{ cp2=pval; cp++; for(;;){ if(*cp=='\0') return APR_EGENERAL; if(*cp == '\\'){ cp++; if (*cp!='\0') *cp2++=*cp++; continue; } if (*cp=='\''){ *cp2='\0'; cp++; break; } *cp2++=*cp++; } } /* Now we have the name and the value. See if it's a valid option. */ for (option=*options;option;option++){ if (!strcmp(option,pname)) break; } if(!option) return APR_EGENERAL; /* Store the value */ if(!strcmp(option,"host")){ *host=pval; } else if(!strcmp(option,"port")){ if(apr_isdigit(*pval)) *port=(unsigned int)atoi(pval); else *usocket=pval; } else if(!strcmp(option,"dbname")){ *dbname=pval; } else if(!strcmp(option,"user")){ *user=pval; } else if(!strcmp(option,"password")){ *password=pval; } } return APR_SUCCESS; } #endif static apr_status_t db_clean(void *data){ rxv_spin_conn_t *conn=(rxv_spin_conn_t *)data; switch(conn->type&RXV_SPIN_CONN_MAXID){ case RXV_SPIN_CONN_PGSQL: #ifdef HAVE_LIBPQ PQfinish(conn->pgconn); #endif break; case RXV_SPIN_CONN_MYSQL: #ifdef HAVE_LIBMYSQLCLIENT mysql_close(conn->myconn); #endif break; default: break; } if(RXV_SPIN_CONN_IS_POOLED(conn)) apr_hash_set(conn->cpool->conns,conn->cinfo,APR_HASH_KEY_STRING,NULL); return APR_SUCCESS; } rxv_spin_conn_t *rxv_spin_db_connect(apr_pool_t *pool, rxv_spin_cpool_t *cpool, const char *conninfo, unsigned char type){ rxv_spin_conn_t *conn; char *cinfo; apr_size_t clen; if(!(pool && conninfo) || typeRXV_SPIN_CONN_MAXID) return NULL; /* allocate memory for the connection identification string */ if(!(cinfo=apr_palloc(pool,(clen=strlen(conninfo))+2))) return NULL; /* make the connection identification string: type + connect string */ *cinfo=type; apr_cpystrn(cinfo+1,conninfo,clen+1); /* try to reuse connection from the pool */ if(cpool && (conn=(rxv_spin_conn_t*) apr_hash_get(cpool->conns,cinfo,APR_HASH_KEY_STRING))) return conn; /* connection not found, we'll set one up */ if(!(conn=apr_pcalloc(pool,sizeof(*conn)))) return NULL; switch(type){ case RXV_SPIN_CONN_PGSQL: #ifdef HAVE_LIBPQ if(!(conn->pgconn=PQconnectdb(conninfo))) return NULL; if(PQstatus(conn->pgconn)==CONNECTION_BAD){ PQfinish(conn->pgconn); return NULL; } #else return NULL; #endif break; case RXV_SPIN_CONN_MYSQL: #ifdef HAVE_LIBMYSQLCLIENT if((conn->myconn=mysql_init(NULL))){ char *host,*user,*password,*dbname,*usocket; unsigned int port; /* parsing of the connect string goes here */ if(conninfo_parse(conninfo,pool,&host,&user,&password, &dbname,&usocket,&port)!=APR_SUCCESS){ mysql_close(conn->myconn); return NULL; } if(!mysql_real_connect(conn->myconn,host,user,password,dbname, port,usocket,0)){ mysql_close(conn->myconn); return NULL; } } else{ return NULL; } #else return NULL; #endif break; default: return NULL; break; } if(cpool){ rxv_spin_conn_t *tmp=conn; /* we have to copy into the connection pool now */ if(!((conn=apr_pcalloc(cpool->pool,sizeof(*conn))) && (conn->cinfo=apr_pstrdup(cpool->pool,cinfo)))){ switch(type){ case RXV_SPIN_CONN_PGSQL: #ifdef HAVE_LIBPQ PQfinish(tmp->pgconn); #endif break; case RXV_SPIN_CONN_MYSQL: #ifdef HAVE_LIBMYSQLCLIENT mysql_close(tmp->myconn); #endif break; default: break; } return NULL; } conn->type=(type|RXV_SPIN_CONN_POOLED); conn->conn=tmp->conn; conn->cpool=cpool; conn->cleanup=db_clean; /* the key, full indentification string: type + connect string */ apr_hash_set(cpool->conns,conn->cinfo,APR_HASH_KEY_STRING,conn); apr_pool_cleanup_register(cpool->pool,conn,db_clean,db_clean); } else{ conn->type=type; conn->cinfo=cinfo; conn->pool=pool; conn->cleanup=db_clean; apr_pool_cleanup_register(pool,conn,db_clean,db_clean); } return conn; } /* result cleanup functions, registered with the temporary pool */ #ifdef HAVE_LIBPQ static apr_status_t pgresult_clean(void *data){ PQclear((PGresult *)data); return APR_SUCCESS; } #endif #ifdef HAVE_LIBMYSQLCLIENT static apr_status_t myresult_clean(void *data){ mysql_free_result((MYSQL_RES *)data); return APR_SUCCESS; } #endif /* query (exec) related functions */ rxv_spin_db_result_t *rxv_spin_db_exec(apr_pool_t *pool, rxv_spin_conn_t *conn, const char *query){ rxv_spin_db_result_t *result=NULL; #ifdef HAVE_LIBPQ int pgnrows,pgncols; PGresult *pgresult; #endif #ifdef HAVE_LIBMYSQLCLIENT my_ulonglong mynrows; unsigned int myncols; MYSQL_RES *myresult; #endif if(!(pool && conn && query)) return NULL; if(!(result=apr_pcalloc(pool,sizeof(*result)))) return NULL; switch(conn->type&RXV_SPIN_CONN_MAXID){ case RXV_SPIN_CONN_PGSQL: #ifdef HAVE_LIBPQ /* maybe we need to restart the connection */ if(PQstatus(conn->pgconn)==CONNECTION_BAD){ PQreset(conn->pgconn); if(PQstatus(conn->pgconn)==CONNECTION_BAD){ /* we're screwed for sure */ return NULL; } } if(!(pgresult=PQexec(conn->pgconn,query))) return NULL; /* make sure the result goes away with the temporary pool */ apr_pool_cleanup_register(pool,pgresult,pgresult_clean, apr_pool_cleanup_null); switch(PQresultStatus(pgresult)){ case PGRES_TUPLES_OK: pgnrows=PQntuples(pgresult); pgncols=PQnfields(pgresult); if((result->data=apr_pcalloc(pool,sizeof(*result->data)))){ int i,j; result->data->type=RXV_SPIN_DATA_RWS; result->data->size=pgnrows; result->data->cols=apr_hash_make(pool); for(j=0;jdata->cols,PQfname(pgresult,j), APR_HASH_KEY_STRING,array); for(i=0;itype=RXV_SPIN_DATA_SGL; single->size=PQgetlength(pgresult,i,j); single->data=PQgetvalue(pgresult,i,j); } } else{ return NULL; } } result->status=APR_SUCCESS; } else{ result->status=APR_EGENERAL; } break; case PGRES_COMMAND_OK: result->status=APR_SUCCESS; break; default: result->status=APR_EGENERAL; break; } result->error=PQresultErrorMessage(pgresult); return result; #endif break; case RXV_SPIN_CONN_MYSQL: #ifdef HAVE_LIBMYSQLCLIENT /* MySQL will attempt to reconnect, so we don't have to worry about it */ if(mysql_query(conn->myconn,query) || mysql_errno(conn->myconn)) return NULL; switch(myncols=mysql_field_count(conn->myconn)){ case 0: result->status=APR_SUCCESS; break; default: /* if we can't store the result, we're in trouble */ if(!(myresult=mysql_store_result(conn->myconn))) return NULL; /* make sure the result goes away with the temporary pool */ apr_pool_cleanup_register(pool,myresult,myresult_clean, apr_pool_cleanup_null); mynrows=mysql_num_rows(myresult); if((result->data=apr_pcalloc(pool,sizeof(*result->data)))){ my_ulonglong i; unsigned int j; MYSQL_FIELD *fields=mysql_fetch_fields(myresult); rxv_spin_data_t **arrays,*array; result->data->type=RXV_SPIN_DATA_RWS; result->data->size=mynrows; result->data->cols=apr_hash_make(pool); if(!(arrays=apr_palloc(pool,sizeof(*arrays)*myncols))) return NULL; for(j=0;jdata->cols,fields[j].name, APR_HASH_KEY_STRING,(arrays[j]=array)); else return NULL; } for(i=0;itype=RXV_SPIN_DATA_SGL; single->size=lengths[j]; single->data=row[j]; } } result->status=APR_SUCCESS; } else{ result->status=APR_EGENERAL; } break; } result->error=apr_pstrdup(pool,mysql_error(conn->myconn)); return result; #endif break; default: break; } return NULL; } char *rxv_spin_db_info(rxv_spin_conn_t *conn,unsigned char what){ char *info=NULL; if(!conn) return NULL; switch(conn->type&RXV_SPIN_CONN_MAXID){ case RXV_SPIN_CONN_PGSQL: #ifdef HAVE_LIBPQ switch(what){ case RXV_SPIN_DB_INFO_DB: info=PQdb(conn->pgconn); break; case RXV_SPIN_DB_INFO_USER: info=PQuser(conn->pgconn); break; case RXV_SPIN_DB_INFO_PASS: info=PQpass(conn->pgconn); break; case RXV_SPIN_DB_INFO_HOST: info=PQhost(conn->pgconn); break; case RXV_SPIN_DB_INFO_PORT: info=PQport(conn->pgconn); break; case RXV_SPIN_DB_INFO_TTY: info=PQtty(conn->pgconn); break; case RXV_SPIN_DB_INFO_OPTIONS: info=PQoptions(conn->pgconn); break; case RXV_SPIN_DB_INFO_ERROR: info=PQerrorMessage(conn->pgconn); break; default: break; } #endif break; case RXV_SPIN_CONN_MYSQL: #ifdef HAVE_LIBMYSQLCLIENT switch(what){ case RXV_SPIN_DB_INFO_DB: case RXV_SPIN_DB_INFO_USER: case RXV_SPIN_DB_INFO_PASS: case RXV_SPIN_DB_INFO_HOST: case RXV_SPIN_DB_INFO_PORT: case RXV_SPIN_DB_INFO_TTY: case RXV_SPIN_DB_INFO_OPTIONS: info=""; /* MySQL API can't give us any of those */ break; case RXV_SPIN_DB_INFO_ERROR: info=(char*)mysql_error(conn->myconn); break; default: break; } #endif break; default: break; } return info; } apr_status_t rxv_spin_db_status(rxv_spin_conn_t *conn){ if(!conn) return APR_EGENERAL; switch(conn->type&RXV_SPIN_CONN_MAXID){ case RXV_SPIN_CONN_PGSQL: #ifdef HAVE_LIBPQ if(PQstatus(conn->pgconn)==CONNECTION_OK) return APR_SUCCESS; else return APR_EGENERAL; #endif break; case RXV_SPIN_CONN_MYSQL: #ifdef HAVE_LIBMYSQLCLIENT if(mysql_ping(conn->myconn)) return APR_EGENERAL; else return APR_SUCCESS; #endif break; default: break; } return APR_EGENERAL; } char *rxv_spin_db_escape(apr_pool_t *pool, rxv_spin_conn_t *conn, const char *str){ char *escstr; size_t length; if(!(pool && conn && str)) return NULL; length=strlen(str); if(!(escstr=apr_palloc(pool,length*2+1))) return NULL; switch(conn->type&RXV_SPIN_CONN_MAXID){ case RXV_SPIN_CONN_PGSQL: #ifdef HAVE_LIBPQ PQescapeString(escstr,str,length); return escstr; #endif break; case RXV_SPIN_CONN_MYSQL: #ifdef HAVE_LIBMYSQLCLIENT mysql_real_escape_string(conn->myconn,escstr,str,length); return escstr; #endif break; default: break; } return NULL; } apr_status_t rxv_spin_db_reset(rxv_spin_conn_t *conn){ if(!conn) return APR_EGENERAL; switch(conn->type&RXV_SPIN_CONN_MAXID){ case RXV_SPIN_CONN_PGSQL: #ifdef HAVE_LIBPQ PQreset(conn->pgconn); #endif break; case RXV_SPIN_CONN_MYSQL: #ifdef HAVE_LIBMYSQLCLIENT mysql_ping(conn->myconn); #endif break; default: break; } return APR_SUCCESS; }