/* * Copyright (C) 2007 Tildeslash Ltd. All rights reserved. * * 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. * * There are special exceptions to the terms and conditions of the GPL * as it is applied to this software. View the full text of the exception * in the file EXCEPTIONS accompanying this software distribution. * * 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 */ #include "Config.h" #include #include #include #include #include "URL.h" #include "Util.h" #include "ResultSet.h" #include "StringBuffer.h" #include "PreparedStatement.h" #include "PostgresqlResultSet.h" #include "PostgresqlPreparedStatement.h" #include "ConnectionStrategy.h" #include "PostgresqlConnection.h" #define MAXPARAM "999" /** * Implementation of the Connection/Strategy interface for postgresql. * * @version \$Id: PostgresqlConnection.c,v 1.10 2007/02/11 20:57:20 martinp Exp $ * @file */ /* ----------------------------------------------------------- Definitions */ const struct conop postgresqlconops= { "postgresql", PostgresqlConnection_new, PostgresqlConnection_free, PostgresqlConnection_setQueryTimeout, PostgresqlConnection_setMaxRows, PostgresqlConnection_ping, PostgresqlConnection_beginTransaction, PostgresqlConnection_commit, PostgresqlConnection_rollback, PostgresqlConnection_lastRowId, PostgresqlConnection_rowsChanged, PostgresqlConnection_execute, PostgresqlConnection_executeQuery, PostgresqlConnection_prepareStatement, PostgresqlConnection_getLastError }; #define T IConnection_T struct T { URL_T url; PGconn *db; PGresult *res; int maxRows; int timeout; ExecStatusType lastError; }; extern const struct rsetop postgresqlrsetops; extern const struct prepop postgresqlprepops; /* ------------------------------------------------------------ Prototypes */ static PGconn *doConnect(URL_T url, char **error); /* ----------------------------------------------------- Protected methods */ #ifdef PACKAGE_PROTECTED #pragma GCC visibility push(hidden) #endif T PostgresqlConnection_new(URL_T url, char **error) { T C; PGconn *db; assert(url); assert(error); if(! (db= doConnect(url, error))) return NULL; NEW(C); C->db= db; C->res= NULL; C->url= url; return C; } void PostgresqlConnection_free(T *C) { assert(C && *C); PQclear((*C)->res); PQfinish((*C)->db); FREE(*C); } void PostgresqlConnection_setQueryTimeout(T C, int ms) { assert(C); C->timeout= ms; } void PostgresqlConnection_setMaxRows(T C, int max) { assert(C); C->maxRows= max; } int PostgresqlConnection_ping(T C) { assert(C); return (PQstatus(C->db)==CONNECTION_OK); } int PostgresqlConnection_beginTransaction(T C) { PGresult *res; assert(C); res= PQexec(C->db, "BEGIN TRANSACTION;"); C->lastError= PQresultStatus(res); PQclear(res); return (C->lastError == PGRES_COMMAND_OK); } int PostgresqlConnection_commit(T C) { PGresult *res; assert(C); res= PQexec(C->db, "COMMIT TRANSACTION;"); C->lastError= PQresultStatus(res); PQclear(res); return (C->lastError == PGRES_COMMAND_OK); } int PostgresqlConnection_rollback(T C) { PGresult *res; assert(C); res= PQexec(C->db, "ROLLBACK TRANSACTION;"); C->lastError= PQresultStatus(res); PQclear(res); return (C->lastError == PGRES_COMMAND_OK); } long long int PostgresqlConnection_lastRowId(T C) { assert(C); return (long long int)PQoidValue(C->res); } long long int PostgresqlConnection_rowsChanged(T C) { int e = FALSE; assert(C); return(Util_parseLLong(PQcmdTuples(C->res), &e) && !e); } int PostgresqlConnection_execute(T C, const char *sql, va_list ap) { StringBuffer_T sb; assert(C); sb= StringBuffer_new(""); StringBuffer_vappend(sb, sql, ap); PQclear(C->res); C->res= PQexec(C->db, StringBuffer_toString(sb)); StringBuffer_free(&sb); C->lastError= PQresultStatus(C->res); return (C->lastError == PGRES_COMMAND_OK); } ResultSet_T PostgresqlConnection_executeQuery(T C, const char *sql, va_list ap) { StringBuffer_T sb; assert(C); sb= StringBuffer_new(""); StringBuffer_vappend(sb, sql, ap); PQclear(C->res); C->res= PQexec(C->db, StringBuffer_toString(sb)); StringBuffer_free(&sb); C->lastError= PQresultStatus(C->res); if(C->lastError == PGRES_TUPLES_OK) { return ResultSet_new(PostgresqlResultSet_new(C->res, C->maxRows, FALSE), (Rop_T)&postgresqlrsetops); } return NULL; } PreparedStatement_T PostgresqlConnection_prepareStatement(T C, const char *sql) { int len = 0, prm1 = 0, prm2 = 0, maxparam = atoi(MAXPARAM); long index[maxparam]; char *name = NULL; char *stmt = NULL; char *p = NULL; char *q = NULL; assert(C); assert(sql); /* We have to replace the wildcard '?' using the '$x' here. * We support just 999 parameters currently, but it should * be probably enough - in the case that more will be needed, * we can rise the limit via the MAXPARAM */ p = q = Util_strdup(sql); memset(index, 0, sizeof(index)); index[0] = (long)p; while(prm1 < maxparam && (p= strchr(p, '?'))) { prm1++; *p = 0; p++; index[prm1] = (long)p; } if(!prm1) { stmt = Util_strdup(sql); } else if(prm1 > maxparam) { DEBUG("Prepared statement limit is %d parameters\n", maxparam); FREE(q); return NULL; } else { len = strlen(sql) + prm1 * strlen(MAXPARAM + 1); stmt = CALLOC(1, len + 1); while(prm2 <= prm1) { sprintf(stmt + strlen(stmt), "%s", (char *)index[prm2]); if(prm2 < prm1) sprintf(stmt + strlen(stmt), "$%d", prm2 + 1); prm2++; } } FREE(q); /* Get the unique prepared statement ID */ name = Util_getString("%d", rand()); PQclear(C->res); C->res = PQprepare(C->db, name, stmt, prm1, NULL); FREE(stmt); if(C->lastError == PGRES_COMMAND_OK || C->lastError == PGRES_TUPLES_OK) { return PreparedStatement_new(PostgresqlPreparedStatement_new(C->db, C->maxRows, name, prm1), (Pop_T)&postgresqlprepops); } return NULL; } const char *PostgresqlConnection_getLastError(T C) { assert(C); return PQresultErrorMessage(C->res); } #ifdef PACKAGE_PROTECTED #pragma GCC visibility pop #endif /* ------------------------------------------------------- Private methods */ static PGconn *doConnect(URL_T url, char **error) { #define ERROR(e) do {*error= Util_strdup(e); goto error;} while(0) int port; int ssl = FALSE; int connectTimeout = SQL_DEFAULT_TCP_TIMEOUT; const char *user, *password, *host, *database, *timeout; char *conninfo; PGconn *db = NULL; if(!(user= URL_getUser(url))) if(!(user= URL_getParameter(url, "user"))) ERROR("no username specified in URL"); if(!(password= URL_getPassword(url))) if(!(password= URL_getParameter(url, "password"))) ERROR("no password specified in URL"); if(!(host= URL_getHost(url))) ERROR("no host specified in URL"); if((port= URL_getPort(url))<=0) ERROR("no port specified in URL"); if(!(database= URL_getPath(url))) ERROR("no database specified in URL"); else database++; if(IS(URL_getParameter(url, "use-ssl"), "true")) ssl= TRUE; if((timeout= URL_getParameter(url, "connect-timeout"))) { int e= FALSE; connectTimeout = Util_parseInt(timeout, &e); if(connectTimeout<=0 || e) ERROR("invalid connect timeout value"); } conninfo = Util_getString(" host='%s'" " port=%d" " dbname='%s'" " user='%s'" " password='%s'" " connect_timeout=%d" " sslmode='%s'", host, port, database, user, password, connectTimeout, ssl?"require":"disable" ); db = PQconnectdb(conninfo); FREE(conninfo); if(PQstatus(db) != CONNECTION_OK) { *error= Util_getString("%s", PQerrorMessage(db)); goto error; } return db; error: PQfinish(db); return NULL; }