/*
	CVSNT Generic API
    Copyright (C) 2005 Tony Hoyle and March-Hare Software Ltd

    This library 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 library 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 library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
#endif
#include <config.h>
#include "../lib/api_system.h"

#ifdef HAVE_LIBPQ_FE_H
#include <libpq-fe.h>
#else
// else what??
#endif

#include "../cvs_string.h"
#include "../FileAccess.h"
#include "../ServerIO.h"
#include "PostgresConnection.h"
#include "PostgresRecordset.h"

extern "C" CSqlConnection *Postgres_Alloc()
{
        return new CPostgresConnection;
}

CPostgresConnection::CPostgresConnection()
{
	m_pDb=NULL;
	m_lasterr=PGRES_COMMAND_OK;
}

CPostgresConnection::~CPostgresConnection()
{
	Close();
}

bool CPostgresConnection::Create(const char *host, const char *database, const char *username, const char *password)
{
	if(!Open(host,"template1",username,password))
		return false;
	Execute("create database %s",database);
	if(Error())
		return false;
	Close();
	return Open(host,database,username,password);
}

bool CPostgresConnection::Open(const char *host, const char *database, const char *username, const char *password)
{
	return __Open(host,database,username,password);
}

bool CPostgresConnection::__Open(const char *host, const char *database, const char *username, const char *password)
{
#ifdef _WIN32
	__try
	{
#endif
		char tmp[1024];
		snprintf(tmp,sizeof(tmp),"host = '%s' dbname = '%s' user = '%s' password = '%s'",host,database,username,password);

		m_pDb = PQconnectdb(tmp);
		if(!m_pDb)
			return false;

		if(PQstatus(m_pDb)==CONNECTION_BAD)
			return false;

		PQsetClientEncoding(m_pDb,"UNICODE");
#ifdef _WIN32
	}
	__except (EXCEPTION_EXECUTE_HANDLER)
	{
		return false;
	}
#endif
	return true;
}

bool CPostgresConnection::Close()
{
	if(m_pDb)
		PQfinish(m_pDb);

	m_pDb=NULL;
	return true;
}

bool CPostgresConnection::IsOpen()
{
	if(m_pDb && PQstatus(m_pDb)!=CONNECTION_BAD)
		return true;
	return false;
}

CSqlRecordsetPtr CPostgresConnection::Execute(const char *string, ...)
{
	cvs::string str;
	va_list va;
	va_start(va,string);
	cvs::vsprintf(str,64,string,va);
	va_end(va);

	CPostgresRecordset *rs = new CPostgresRecordset();

	/* postgres doesn't support the standard questionmark operator, instead using '$n'.
	   preparse the string looking for these and replacing them.  This routine is probably
	   not very robust. */
	size_t pos = 0;
	int var = 1;
	char varstr[32];
	bool quote = false;
	while(pos<str.size())
	{
		char c=str[pos];
		if(!quote && c=='\'')
			quote=true;
		else if(quote && c=='\'')
			quote=false;
		else if(!quote && c=='?')
		{
			snprintf(varstr,sizeof(varstr),"$%d",var++);
			str.replace(pos,1,varstr);
		}
		pos++;
	}

	CServerIo::trace(3,"%s",str.c_str());

	PGresult *rslt;
	int numParams = (int)m_bindVars.size();
	Oid *paramTypes = NULL;
	char **paramValues = NULL;
	int *paramLength = NULL;
	int *paramFormats = NULL;
	// Appears to be impossible to define in VC.. Probably an MS bug.
	typedef char __dummy[16];
	__dummy *paramVars = NULL;
	if(numParams)
	{
		paramTypes = new Oid[numParams];
		paramValues = new char *[numParams];
		paramLength = new int[numParams];
		paramFormats = new int[numParams];
		paramVars = new char[numParams][16];

		int n = 0;
		for(std::map<int,CSqlVariant>::iterator i = m_bindVars.begin(); i!=m_bindVars.end(); ++i)
		{
			paramFormats[n]=1;
			switch(i->second.type())
			{
			case CSqlVariant::vtNull:
				paramTypes[n]=0; 
				paramValues[n]=0;
				paramLength[n]=0;
				break;
			case CSqlVariant::vtChar:
			case CSqlVariant::vtUChar:
				paramTypes[n]=18;
				*(char*)paramVars[n]=(char)i->second;
				paramValues[n]=paramVars[n];
				paramLength[n]=sizeof(char);
				break;
			case CSqlVariant::vtShort:
			case CSqlVariant::vtUShort:
				paramTypes[n]=21;
				*(short*)paramVars[n]=(short)i->second;
				paramValues[n]=paramVars[n];
				paramLength[n]=sizeof(short);
				break;
			case CSqlVariant::vtInt:
			case CSqlVariant::vtUInt:
			case CSqlVariant::vtLong:
			case CSqlVariant::vtULong:
				if(sizeof(int)==4)
				{
					paramTypes[n]=23; 
					*(int*)paramVars[n]=(int)i->second;
					paramValues[n]=paramVars[n];
					paramLength[n]=sizeof(int);
					break;
				}
			/* fall through (64bit systems) */
			case CSqlVariant::vtLongLong:
			case CSqlVariant::vtULongLong:
				paramTypes[n]=20;
	#ifdef _WIN32
				*(__int64*)paramVars[n]=(__int64)i->second;
				paramValues[n]=paramVars[n];
				paramLength[n]=sizeof(__int64);
	#else
				*(long long*)paramVars[n]=(long long)i->second;
				paramValues[n]=paramVars[n];
				paramLength[n]=sizeof(long long);
	#endif
				break;
			case CSqlVariant::vtString:
			case CSqlVariant::vtWString:
				{
				paramTypes[n]=25;
				const char *s = i->second;
				paramLength[n]=(int)strlen(s);
				paramValues[n]=(char*)s;
				}
				break;
			}
			n++;
		}
	}


	rslt = PQexecParams(m_pDb, str.c_str(), numParams, paramTypes, paramValues, paramLength, paramFormats, 1);

	if(paramTypes)
		delete[] paramTypes;
	if(paramValues)
		delete[] paramValues;
	if(paramLength)
		delete[] paramLength;
	if(paramFormats)
		delete[] paramFormats;
	if(paramVars)
		delete[] paramVars;

	if(!rslt)
	{
		m_lasterr = PGRES_FATAL_ERROR;
		return rs;
	}

	m_lasterr = PQresultStatus(rslt);
	if(m_lasterr == PGRES_BAD_RESPONSE || m_lasterr == PGRES_NONFATAL_ERROR || m_lasterr == PGRES_FATAL_ERROR)
	{
		if(rslt)
			m_lasterrmsg = PQresultErrorMessage(rslt);
		else
			m_lasterrmsg = "";
		if(m_lasterrmsg.size() && m_lasterrmsg[m_lasterrmsg.size()-1]=='\n')
			m_lasterrmsg.resize(m_lasterrmsg.size()-1);
		PQclear(rslt);
		return rs;
	}

	rs->Init(m_pDb,rslt);

	m_bindVars.clear();

	return rs;
}

bool CPostgresConnection::Error() const
{
	if(!m_pDb) return true;
	if(PQstatus(m_pDb)==CONNECTION_BAD) return true;
	if(m_lasterr == PGRES_BAD_RESPONSE || m_lasterr == PGRES_NONFATAL_ERROR || m_lasterr == PGRES_FATAL_ERROR)
		return true;
	return false;
}

const char *CPostgresConnection::ErrorString() 
{
	if(!m_pDb)
		return "Database not created or couldn't find libpq.dll";
	if(PQstatus(m_pDb)!=CONNECTION_OK)
		return PQerrorMessage(m_pDb);
	if(m_lasterrmsg.size())
		return m_lasterrmsg.c_str();
	return PQresStatus((ExecStatusType)m_lasterr);
}

unsigned CPostgresConnection::GetInsertIdentity(const char *table_hint) 
{
	cvs::string str;
	cvs::sprintf(str,80,"select currval('%s_id_seq')",table_hint);
	PGresult *rslt = PQexec(m_pDb, str.c_str());
	if(!PQntuples(rslt) || !PQnfields(rslt))
	{
		CServerIo::trace(1,"Postgres GetInsertIdentity(%s) failed",table_hint);
		return 0;
	}

	unsigned long ident;
	if(sscanf(PQgetvalue(rslt,0,0),"%lu",&ident)!=1)
	{
		CServerIo::trace(1,"Postgres GetInsertIdentity(%s) failed (bogus value)",table_hint);
		return 0;
	}

	PQclear(rslt);

	return ident;
}

bool CPostgresConnection::BeginTrans()
{
	PGresult *rslt = PQexec(m_pDb, "BEGIN TRANSACTION");
	m_lasterr = PQresultStatus(rslt);
	PQclear(rslt);
	if(m_lasterr == PGRES_BAD_RESPONSE || m_lasterr == PGRES_NONFATAL_ERROR || m_lasterr == PGRES_FATAL_ERROR)
		return false;
	return true;
}

bool CPostgresConnection::CommitTrans()
{
	PGresult *rslt = PQexec(m_pDb, "COMMIT TRANSACTION");
	m_lasterr = PQresultStatus(rslt);
	PQclear(rslt);
	if(m_lasterr == PGRES_BAD_RESPONSE || m_lasterr == PGRES_NONFATAL_ERROR || m_lasterr == PGRES_FATAL_ERROR)
		return false;
	return true;
}

bool CPostgresConnection::RollbackTrans()
{
	PGresult *rslt = PQexec(m_pDb, "ROLLBACK TRANSACTION");
	m_lasterr = PQresultStatus(rslt);
	PQclear(rslt);
	if(m_lasterr == PGRES_BAD_RESPONSE || m_lasterr == PGRES_NONFATAL_ERROR || m_lasterr == PGRES_FATAL_ERROR)
		return false;
	return true;
}

bool CPostgresConnection::Bind(int variable, CSqlVariant value)
{
	m_bindVars[variable]=value;
	return true;
}


syntax highlighted by Code2HTML, v. 0.9.1