Add PostgreSQL database driver (#32)
* Add PostgreSQL DBI extension Bug #3849 This adds a postgresql database driver called "pgsql". The ambuild script changes could be very wrong ;) * Add pgsql support to clientprefs This was originally made by Lyfe in bug 3849! Thanks for that. PostgreSQL supports the 'IF NOT EXISTS' clause when creating tables since version 9.1, so i've switched to use that. * Added pgsql support to sql-admin-manager * Add --pgsql-path to ambuild configure * More ambuild fixes * Compile libpq with ambuild * Try to generate postgres configs * Add dummy config file * More ambuild fixes * Get errorcode and readable error in preparequery No need to stop getting the errorCode, if the error string should be retrieved. Just return both! * Fix indentation in AMBuilderPGSQL * Try to patch and configure postgres through ambuild * Revert "Try to patch and configure postgres through ambuild" This reverts commit 68dfc82b8eb0ce11f621af67ec247b5f47e4a189. * Update to use PostgreSQL 9.4 * Move postgres preparation into seperate script Fetching, preparing and configuring of the postgresql sourcecode is done in a seperate prepare_postgresql.sh script now. People can use this script for their manual builds, if they don't want to use checkout-deps.sh. * Add patch to configure.in again Remove the version check for autoconf in postgres 9.4 too. * Nit: Support older objdirs for hasPgSql ambuild option * Update to use PostgreSQL 9.4.6 * Use newer AMTL and PGSQL 9.6 * Fix threaded queries When running the thread part of a query, it already gets the atomic lock. Don't try to get it twice in that case. Use a seperate lock to protect access to the lastInsertID and lastAffectedRows variables. * Fix SetCharacterSet racing with threaded queries Same fix like in the mysql driver. * Use ke::AutoLock for lastInsertID mutex * Fix fetching one more row than available Don't try to access a row that doesn't exist when iterating rows. * Improve sanity checks on column access Don't call libpq functions with out of bounds column indices. * Let QuoteString return false if quoting failed * Fix build for x64 support changes and update to PGSQL 9.6.9 * Fix linux build * Fix building of connection options string snprintf doesn't support overlapping buffers. * Update to PostgreSQL 9.6.15 * Fix crash after freeing first IQuery object * Fix code crunch * Fix memory leak, cleanup * Nuke MSVC project and Makefile * Remove unsupported numeric error codes * Use STL threads * Add prebuilt libpq for Linux and Windows * Restore and fix version.rc file * Add PostgreSQL build instructions * Add prebuilt libpq for Mac * Add win64 libpq build This is version 9.6.15 since that's what I still had laying around. * Fix buildscript * Fix x64 build on linux and mac Co-authored-by: Erik Minekus <[email protected]>
This commit is contained in:
co-authored by
Erik Minekus
parent
3c30f7b971
commit
e5342afe2a
@@ -0,0 +1,350 @@
|
||||
/**
|
||||
* vim: set ts=4 :
|
||||
* =============================================================================
|
||||
* SourceMod PostgreSQL Extension
|
||||
* Copyright (C) 2013 AlliedModders LLC. 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, version 3.0, as published by the
|
||||
* Free Software Foundation.
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* As a special exception, AlliedModders LLC gives you permission to link the
|
||||
* code of this program (as well as its derivative works) to "Half-Life 2," the
|
||||
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
|
||||
* by the Valve Corporation. You must obey the GNU General Public License in
|
||||
* all respects for all other code used. Additionally, AlliedModders LLC grants
|
||||
* this exception to all derivative works. AlliedModders LLC defines further
|
||||
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
|
||||
* or <http://www.sourcemod.net/license.php>.
|
||||
*
|
||||
* Version: $Id$
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include "PgBasicResults.h"
|
||||
|
||||
PgBasicResults::PgBasicResults(PGresult *res)
|
||||
: m_pRes(res)
|
||||
{
|
||||
Update();
|
||||
}
|
||||
|
||||
PgBasicResults::~PgBasicResults()
|
||||
{
|
||||
}
|
||||
|
||||
void PgBasicResults::Update()
|
||||
{
|
||||
if (m_pRes)
|
||||
{
|
||||
m_ColCount = (unsigned int)PQnfields(m_pRes);
|
||||
m_RowCount = (unsigned int)PQntuples(m_pRes);
|
||||
m_CurRow = 0;
|
||||
m_RowFetched = false;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int PgBasicResults::GetRowCount()
|
||||
{
|
||||
return m_RowCount;
|
||||
}
|
||||
|
||||
unsigned int PgBasicResults::GetFieldCount()
|
||||
{
|
||||
return m_ColCount;
|
||||
}
|
||||
|
||||
bool PgBasicResults::FieldNameToNum(const char *name, unsigned int *columnId)
|
||||
{
|
||||
int id = PQfnumber(m_pRes, name);
|
||||
|
||||
if (id == -1)
|
||||
return false;
|
||||
|
||||
*columnId = (unsigned int)id;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const char *PgBasicResults::FieldNumToName(unsigned int colId)
|
||||
{
|
||||
if (colId >= m_ColCount)
|
||||
return nullptr;
|
||||
|
||||
return PQfname(m_pRes, colId);
|
||||
}
|
||||
|
||||
bool PgBasicResults::MoreRows()
|
||||
{
|
||||
return ((!m_RowFetched && m_RowCount > 0) || (m_RowFetched && m_CurRow < m_RowCount-1));
|
||||
}
|
||||
|
||||
IResultRow *PgBasicResults::FetchRow()
|
||||
{
|
||||
// Rows start at 0. Start incrementing the current row when fetching the second time.
|
||||
if (m_RowFetched)
|
||||
m_CurRow++;
|
||||
|
||||
// Reached the end of the rows.
|
||||
if (m_CurRow >= m_RowCount)
|
||||
{
|
||||
m_CurRow = m_RowCount;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// We fetched some rows.
|
||||
m_RowFetched = true;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
IResultRow *PgBasicResults::CurrentRow()
|
||||
{
|
||||
if (!m_pRes
|
||||
|| !m_RowFetched
|
||||
|| m_CurRow >= m_RowCount)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
bool PgBasicResults::Rewind()
|
||||
{
|
||||
m_RowFetched = false;
|
||||
m_CurRow = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
DBType PgBasicResults::GetFieldType(unsigned int field)
|
||||
{
|
||||
if (field >= m_ColCount)
|
||||
{
|
||||
return DBType_Unknown;
|
||||
}
|
||||
|
||||
Oid fld = PQftype(m_pRes, field);
|
||||
if (fld == InvalidOid)
|
||||
{
|
||||
return DBType_Unknown;
|
||||
}
|
||||
|
||||
return GetOurType(fld);
|
||||
}
|
||||
|
||||
DBType PgBasicResults::GetFieldDataType(unsigned int field)
|
||||
{
|
||||
if (field >= m_ColCount)
|
||||
{
|
||||
return DBType_Unknown;
|
||||
}
|
||||
|
||||
if (PQfformat(m_pRes, field) == 1)
|
||||
{
|
||||
return DBType_Blob;
|
||||
} else {
|
||||
return DBType_String;
|
||||
}
|
||||
}
|
||||
|
||||
bool PgBasicResults::IsNull(unsigned int columnId)
|
||||
{
|
||||
if (columnId >= m_ColCount)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return (PQgetisnull(m_pRes, m_CurRow, columnId) == 1);
|
||||
}
|
||||
|
||||
DBResult PgBasicResults::GetString(unsigned int columnId, const char **pString, size_t *length)
|
||||
{
|
||||
if (columnId >= m_ColCount)
|
||||
{
|
||||
return DBVal_Error;
|
||||
} else if (IsNull(columnId)) {
|
||||
*pString = "";
|
||||
if (length)
|
||||
{
|
||||
*length = 0;
|
||||
}
|
||||
return DBVal_Null;
|
||||
}
|
||||
|
||||
*pString = PQgetvalue(m_pRes, m_CurRow, columnId);
|
||||
|
||||
if (length)
|
||||
{
|
||||
*length = GetDataSize(columnId);
|
||||
}
|
||||
|
||||
return DBVal_Data;
|
||||
}
|
||||
|
||||
DBResult PgBasicResults::CopyString(unsigned int columnId,
|
||||
char *buffer,
|
||||
size_t maxlength,
|
||||
size_t *written)
|
||||
{
|
||||
DBResult res;
|
||||
const char *str;
|
||||
if ((res=GetString(columnId, &str, nullptr)) == DBVal_Error)
|
||||
{
|
||||
return DBVal_Error;
|
||||
}
|
||||
|
||||
size_t wr = strncopy(buffer, str, maxlength);
|
||||
if (written)
|
||||
{
|
||||
*written = wr;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
size_t PgBasicResults::GetDataSize(unsigned int columnId)
|
||||
{
|
||||
if (columnId >= m_ColCount)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (size_t)PQgetlength(m_pRes, m_CurRow, columnId);
|
||||
}
|
||||
|
||||
DBResult PgBasicResults::GetFloat(unsigned int col, float *fval)
|
||||
{
|
||||
if (col >= m_ColCount)
|
||||
{
|
||||
return DBVal_Error;
|
||||
} else if (IsNull(col)) {
|
||||
*fval = 0.0f;
|
||||
return DBVal_Null;
|
||||
}
|
||||
|
||||
*fval = (float)atof(PQgetvalue(m_pRes, m_CurRow, col));
|
||||
|
||||
return DBVal_Data;
|
||||
}
|
||||
|
||||
DBResult PgBasicResults::GetInt(unsigned int col, int *val)
|
||||
{
|
||||
if (col >= m_ColCount)
|
||||
{
|
||||
return DBVal_Error;
|
||||
} else if (IsNull(col)) {
|
||||
*val = 0;
|
||||
return DBVal_Null;
|
||||
}
|
||||
|
||||
*val = atoi(PQgetvalue(m_pRes, m_CurRow, col));
|
||||
|
||||
return DBVal_Data;
|
||||
}
|
||||
|
||||
DBResult PgBasicResults::GetBlob(unsigned int col, const void **pData, size_t *length)
|
||||
{
|
||||
if (col >= m_ColCount)
|
||||
{
|
||||
return DBVal_Error;
|
||||
} else if (IsNull(col)) {
|
||||
*pData = nullptr;
|
||||
if (length)
|
||||
{
|
||||
*length = 0;
|
||||
}
|
||||
return DBVal_Null;
|
||||
}
|
||||
|
||||
*pData = PQgetvalue(m_pRes, m_CurRow, col);
|
||||
|
||||
if (length)
|
||||
{
|
||||
*length = GetDataSize(col);
|
||||
}
|
||||
|
||||
return DBVal_Data;
|
||||
}
|
||||
|
||||
DBResult PgBasicResults::CopyBlob(unsigned int columnId, void *buffer, size_t maxlength, size_t *written)
|
||||
{
|
||||
const void *addr;
|
||||
size_t length;
|
||||
DBResult res;
|
||||
|
||||
if ((res=GetBlob(columnId, &addr, &length)) == DBVal_Error)
|
||||
{
|
||||
return DBVal_Error;
|
||||
}
|
||||
|
||||
if (!addr)
|
||||
{
|
||||
return DBVal_Null;
|
||||
}
|
||||
|
||||
if (length > maxlength)
|
||||
{
|
||||
length = maxlength;
|
||||
}
|
||||
|
||||
memcpy(buffer, addr, length);
|
||||
if (written)
|
||||
{
|
||||
*written = length;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
PgQuery::PgQuery(PgDatabase *db, PGresult *res)
|
||||
: m_pParent(db), m_rs(res)
|
||||
{
|
||||
m_InsertID = (unsigned int)PQoidValue(res);
|
||||
m_AffectedRows = atoi(PQcmdTuples(res));
|
||||
m_pParent->SetLastIDAndRows(m_InsertID, m_AffectedRows);
|
||||
}
|
||||
|
||||
IResultSet *PgQuery::GetResultSet()
|
||||
{
|
||||
if (!m_rs.m_pRes)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &m_rs;
|
||||
}
|
||||
|
||||
unsigned int PgQuery::GetInsertID()
|
||||
{
|
||||
return m_InsertID;
|
||||
}
|
||||
|
||||
unsigned int PgQuery::GetAffectedRows()
|
||||
{
|
||||
return m_AffectedRows;
|
||||
}
|
||||
|
||||
bool PgQuery::FetchMoreResults()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void PgQuery::Destroy()
|
||||
{
|
||||
if (m_rs.m_pRes != nullptr)
|
||||
{
|
||||
PQclear(m_rs.m_pRes);
|
||||
}
|
||||
|
||||
/* Self destruct */
|
||||
delete this;
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* vim: set ts=4 :
|
||||
* =============================================================================
|
||||
* SourceMod PostgreSQL Extension
|
||||
* Copyright (C) 2013 AlliedModders LLC. 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, version 3.0, as published by the
|
||||
* Free Software Foundation.
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* As a special exception, AlliedModders LLC gives you permission to link the
|
||||
* code of this program (as well as its derivative works) to "Half-Life 2," the
|
||||
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
|
||||
* by the Valve Corporation. You must obey the GNU General Public License in
|
||||
* all respects for all other code used. Additionally, AlliedModders LLC grants
|
||||
* this exception to all derivative works. AlliedModders LLC defines further
|
||||
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
|
||||
* or <http://www.sourcemod.net/license.php>.
|
||||
*
|
||||
* Version: $Id$
|
||||
*/
|
||||
|
||||
#ifndef _INCLUDE_SM_PGSQL_BASIC_RESULTS_H_
|
||||
#define _INCLUDE_SM_PGSQL_BASIC_RESULTS_H_
|
||||
|
||||
#include <amtl/am-refcounting.h>
|
||||
#include "PgDatabase.h"
|
||||
|
||||
class PgQuery;
|
||||
class PgStatement;
|
||||
|
||||
class PgBasicResults :
|
||||
public IResultSet,
|
||||
public IResultRow
|
||||
{
|
||||
friend class PgQuery;
|
||||
friend class PgStatement;
|
||||
public:
|
||||
PgBasicResults(PGresult *res);
|
||||
~PgBasicResults();
|
||||
public: //IResultSet
|
||||
unsigned int GetRowCount();
|
||||
unsigned int GetFieldCount();
|
||||
const char *FieldNumToName(unsigned int columnId);
|
||||
bool FieldNameToNum(const char *name, unsigned int *columnId);
|
||||
bool MoreRows();
|
||||
IResultRow *FetchRow();
|
||||
bool Rewind();
|
||||
DBType GetFieldType(unsigned int field);
|
||||
DBType GetFieldDataType(unsigned int field);
|
||||
IResultRow *CurrentRow();
|
||||
public: //IResultRow
|
||||
DBResult GetString(unsigned int columnId, const char **pString, size_t *length);
|
||||
DBResult GetFloat(unsigned int columnId, float *pFloat);
|
||||
DBResult GetInt(unsigned int columnId, int *pInt);
|
||||
bool IsNull(unsigned int columnId);
|
||||
DBResult GetBlob(unsigned int columnId, const void **pData, size_t *length);
|
||||
DBResult CopyBlob(unsigned int columnId, void *buffer, size_t maxlength, size_t *written);
|
||||
DBResult CopyString(unsigned int columnId,
|
||||
char *buffer,
|
||||
size_t maxlength,
|
||||
size_t *written);
|
||||
size_t GetDataSize(unsigned int columnId);
|
||||
protected:
|
||||
void Update();
|
||||
private:
|
||||
PGresult *m_pRes;
|
||||
bool m_RowFetched;
|
||||
unsigned int m_CurRow;
|
||||
unsigned int m_ColCount;
|
||||
unsigned int m_RowCount;
|
||||
};
|
||||
|
||||
class PgQuery : public IQuery
|
||||
{
|
||||
friend class PgBasicResults;
|
||||
public:
|
||||
PgQuery(PgDatabase *db, PGresult *res);
|
||||
public:
|
||||
IResultSet *GetResultSet();
|
||||
bool FetchMoreResults();
|
||||
void Destroy();
|
||||
public: // Used by the driver to implement GetInsertIDForQuery()/GetAffectedRowsForQuery()
|
||||
unsigned int GetInsertID();
|
||||
unsigned int GetAffectedRows();
|
||||
private:
|
||||
ke::RefPtr<PgDatabase> m_pParent;
|
||||
PgBasicResults m_rs;
|
||||
unsigned int m_InsertID;
|
||||
unsigned int m_AffectedRows;
|
||||
};
|
||||
|
||||
#endif //_INCLUDE_SM_PGSQL_BASIC_RESULTS_H_
|
||||
@@ -0,0 +1,346 @@
|
||||
/**
|
||||
* vim: set ts=4 :
|
||||
* =============================================================================
|
||||
* SourceMod PostgreSQL Extension
|
||||
* Copyright (C) 2013 AlliedModders LLC. 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, version 3.0, as published by the
|
||||
* Free Software Foundation.
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* As a special exception, AlliedModders LLC gives you permission to link the
|
||||
* code of this program (as well as its derivative works) to "Half-Life 2," the
|
||||
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
|
||||
* by the Valve Corporation. You must obey the GNU General Public License in
|
||||
* all respects for all other code used. Additionally, AlliedModders LLC grants
|
||||
* this exception to all derivative works. AlliedModders LLC defines further
|
||||
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
|
||||
* or <http://www.sourcemod.net/license.php>.
|
||||
*
|
||||
* Version: $Id$
|
||||
*/
|
||||
|
||||
#include "PgDatabase.h"
|
||||
#include "smsdk_ext.h"
|
||||
#include "PgBasicResults.h"
|
||||
#include "PgStatement.h"
|
||||
|
||||
// Some selected defines from postgresql 9.2.4's src/include/catalog/pg_type.h
|
||||
// Fast scan to extract the types that shouldn't be read as string.
|
||||
|
||||
// Floats
|
||||
#define FLOAT4OID 700
|
||||
#define FLOAT8OID 701
|
||||
#define NUMERICOID 1700
|
||||
|
||||
// Integer
|
||||
#define BOOLOID 16
|
||||
#define INT8OID 20
|
||||
#define INT2OID 21
|
||||
#define INT4OID 23
|
||||
#define OIDOID 26
|
||||
#define TIDOID 27
|
||||
#define XIDOID 28
|
||||
#define CIDOID 29
|
||||
|
||||
#define ABSTIMEOID 702
|
||||
#define RELTIMEOID 703
|
||||
#define TINTERVALOID 704
|
||||
|
||||
#define REFCURSOROID 1790
|
||||
|
||||
// String
|
||||
#define BYTEAOID 17 // Hrmpf. Could be both, string or blob binary data..
|
||||
#define CHAROID 18
|
||||
#define NAMEOID 19
|
||||
#define TEXTOID 25
|
||||
#define BPCHAROID 1042
|
||||
#define VARCHAROID 1043
|
||||
#define DATEOID 1082
|
||||
#define TIMEOID 1083
|
||||
|
||||
// Blob
|
||||
#define POINTOID 600
|
||||
#define LSEGOID 601
|
||||
#define PATHOID 602
|
||||
#define BOXOID 603
|
||||
#define POLYGONOID 604
|
||||
|
||||
DBType GetOurType(Oid type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case FLOAT4OID:
|
||||
case FLOAT8OID:
|
||||
{
|
||||
return DBType_Float;
|
||||
}
|
||||
case BOOLOID:
|
||||
case INT8OID:
|
||||
case INT2OID:
|
||||
case INT4OID:
|
||||
case OIDOID:
|
||||
case TIDOID:
|
||||
case XIDOID:
|
||||
case CIDOID:
|
||||
case ABSTIMEOID:
|
||||
case RELTIMEOID:
|
||||
case TINTERVALOID:
|
||||
case REFCURSOROID:
|
||||
{
|
||||
return DBType_Integer;
|
||||
}
|
||||
case BYTEAOID:
|
||||
case CHAROID:
|
||||
case NAMEOID:
|
||||
case TEXTOID:
|
||||
case BPCHAROID:
|
||||
case VARCHAROID:
|
||||
case DATEOID:
|
||||
case TIMEOID:
|
||||
{
|
||||
return DBType_String;
|
||||
}
|
||||
|
||||
case POINTOID:
|
||||
case LSEGOID:
|
||||
case PATHOID:
|
||||
case BOXOID:
|
||||
case POLYGONOID:
|
||||
{
|
||||
return DBType_Blob;
|
||||
}
|
||||
default:
|
||||
{
|
||||
return DBType_String;
|
||||
}
|
||||
}
|
||||
|
||||
return DBType_Unknown;
|
||||
}
|
||||
|
||||
PgDatabase::PgDatabase(PGconn *pgsql, const DatabaseInfo *info, bool persistent)
|
||||
: m_pgsql(pgsql), m_lastInsertID(0), m_lastAffectedRows(0), m_preparedStatementID(0),
|
||||
m_bPersistent(persistent)
|
||||
{
|
||||
m_Host.assign(info->host);
|
||||
m_Database.assign(info->database);
|
||||
m_User.assign(info->user);
|
||||
m_Pass.assign(info->pass);
|
||||
|
||||
m_Info.database = m_Database.c_str();
|
||||
m_Info.host = m_Host.c_str();
|
||||
m_Info.user = m_User.c_str();
|
||||
m_Info.pass = m_Pass.c_str();
|
||||
m_Info.driver = NULL;
|
||||
m_Info.maxTimeout = info->maxTimeout;
|
||||
m_Info.port = info->port;
|
||||
|
||||
// DBI, for historical reasons, guarantees an initial refcount of 1.
|
||||
AddRef();
|
||||
}
|
||||
|
||||
PgDatabase::~PgDatabase()
|
||||
{
|
||||
if (m_bPersistent)
|
||||
g_PgDriver.RemoveFromList(this, true);
|
||||
PQfinish(m_pgsql);
|
||||
|
||||
// libpg doesn't keep track of open resultsets of a connection.
|
||||
// maybe we should track all opened results and clear them here?
|
||||
// Otherwise there might be a memory leak, if the plugin doesn't close all result handles properly.
|
||||
// There is no restriction on the order of closing the database connection and the result handles though.
|
||||
}
|
||||
|
||||
void PgDatabase::IncReferenceCount()
|
||||
{
|
||||
AddRef();
|
||||
}
|
||||
|
||||
void PgDatabase::SetLastIDAndRows(unsigned int insertID, unsigned int affectedRows)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_LastQueryInfoLock);
|
||||
// Also remember the last query's insert id and affected rows. postgresql only stores them per query.
|
||||
m_lastInsertID = insertID;
|
||||
m_lastAffectedRows = affectedRows;
|
||||
}
|
||||
|
||||
bool PgDatabase::Close()
|
||||
{
|
||||
return !Release();
|
||||
}
|
||||
|
||||
const DatabaseInfo &PgDatabase::GetInfo()
|
||||
{
|
||||
return m_Info;
|
||||
}
|
||||
|
||||
unsigned int PgDatabase::GetInsertID()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_LastQueryInfoLock);
|
||||
return m_lastInsertID;
|
||||
}
|
||||
|
||||
unsigned int PgDatabase::GetAffectedRows()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_LastQueryInfoLock);
|
||||
return m_lastAffectedRows;
|
||||
}
|
||||
|
||||
const char *PgDatabase::GetError(int *errCode)
|
||||
{
|
||||
if (errCode)
|
||||
{
|
||||
// PostgreSQL only supports SQLSTATE error codes.
|
||||
// https://www.postgresql.org/docs/9.6/errcodes-appendix.html
|
||||
*errCode = -1;
|
||||
}
|
||||
|
||||
return PQerrorMessage(m_pgsql);
|
||||
}
|
||||
|
||||
bool PgDatabase::QuoteString(const char *str, char buffer[], size_t maxlength, size_t *newSize)
|
||||
{
|
||||
size_t size = strlen(str);
|
||||
size_t needed = size * 2 + 1;
|
||||
|
||||
if (maxlength < needed)
|
||||
{
|
||||
if (newSize)
|
||||
{
|
||||
*newSize = (size_t)needed;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int error = 0;
|
||||
needed = PQescapeStringConn(m_pgsql, buffer, str, size, &error);
|
||||
if (newSize)
|
||||
{
|
||||
*newSize = (size_t)needed;
|
||||
}
|
||||
|
||||
return error == 0;
|
||||
}
|
||||
|
||||
bool PgDatabase::DoSimpleQuery(const char *query)
|
||||
{
|
||||
IQuery *pQuery = DoQuery(query);
|
||||
if (!pQuery)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
pQuery->Destroy();
|
||||
return true;
|
||||
}
|
||||
|
||||
IQuery *PgDatabase::DoQuery(const char *query)
|
||||
{
|
||||
PGresult *res = PQexec(m_pgsql, query);
|
||||
|
||||
ExecStatusType status = PQresultStatus(res);
|
||||
|
||||
if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK)
|
||||
{
|
||||
PQclear(res);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return new PgQuery(this, res);
|
||||
}
|
||||
|
||||
bool PgDatabase::DoSimpleQueryEx(const char *query, size_t len)
|
||||
{
|
||||
IQuery *pQuery = DoQueryEx(query, len);
|
||||
if (!pQuery)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
pQuery->Destroy();
|
||||
return true;
|
||||
}
|
||||
|
||||
IQuery *PgDatabase::DoQueryEx(const char *query, size_t len)
|
||||
{
|
||||
// There is no way to send binary data like that in queries.
|
||||
// You'd need to escape the value with PQescapeByteaConn first and use it in the query string as usual.
|
||||
return DoQuery(query);
|
||||
}
|
||||
|
||||
unsigned int PgDatabase::GetAffectedRowsForQuery(IQuery *query)
|
||||
{
|
||||
return static_cast<PgQuery*>(query)->GetAffectedRows();
|
||||
}
|
||||
|
||||
unsigned int PgDatabase::GetInsertIDForQuery(IQuery *query)
|
||||
{
|
||||
return static_cast<PgQuery*>(query)->GetInsertID();
|
||||
}
|
||||
|
||||
IPreparedQuery *PgDatabase::PrepareQuery(const char *query, char *error, size_t maxlength, int *errCode)
|
||||
{
|
||||
char stmtName[10];
|
||||
// Maybe needs some locking around m_preparedStatementID++?
|
||||
unsigned int stmtID = m_preparedStatementID;
|
||||
snprintf(stmtName, 10, "%d", stmtID);
|
||||
m_preparedStatementID++;
|
||||
|
||||
// Let postgresql guess the types of the arguments if there are any..
|
||||
PGresult *res = PQprepare(m_pgsql, stmtName, query, 0, NULL);
|
||||
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
if (error)
|
||||
{
|
||||
strncopy(error, PQresultErrorMessage(res), maxlength);
|
||||
}
|
||||
|
||||
if (errCode)
|
||||
{
|
||||
// PostgreSQL only supports SQLSTATE error codes.
|
||||
// https://www.postgresql.org/docs/9.6/errcodes-appendix.html
|
||||
*errCode = -1;
|
||||
}
|
||||
|
||||
PQclear(res);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Only the statement name is of importance. free the PGresult here.
|
||||
PQclear(res);
|
||||
return new PgStatement(this, stmtName);
|
||||
}
|
||||
|
||||
bool PgDatabase::LockForFullAtomicOperation()
|
||||
{
|
||||
m_FullLock.lock();
|
||||
return true;
|
||||
}
|
||||
|
||||
void PgDatabase::UnlockFromFullAtomicOperation()
|
||||
{
|
||||
m_FullLock.unlock();
|
||||
}
|
||||
|
||||
IDBDriver *PgDatabase::GetDriver()
|
||||
{
|
||||
return &g_PgDriver;
|
||||
}
|
||||
|
||||
bool PgDatabase::SetCharacterSet(const char *characterset)
|
||||
{
|
||||
bool res;
|
||||
LockForFullAtomicOperation();
|
||||
res = PQsetClientEncoding(m_pgsql, characterset) == 0;
|
||||
UnlockFromFullAtomicOperation();
|
||||
return res;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* vim: set ts=4 :
|
||||
* =============================================================================
|
||||
* SourceMod PostgreSQL Extension
|
||||
* Copyright (C) 2013 AlliedModders LLC. 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, version 3.0, as published by the
|
||||
* Free Software Foundation.
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* As a special exception, AlliedModders LLC gives you permission to link the
|
||||
* code of this program (as well as its derivative works) to "Half-Life 2," the
|
||||
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
|
||||
* by the Valve Corporation. You must obey the GNU General Public License in
|
||||
* all respects for all other code used. Additionally, AlliedModders LLC grants
|
||||
* this exception to all derivative works. AlliedModders LLC defines further
|
||||
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
|
||||
* or <http://www.sourcemod.net/license.php>.
|
||||
*
|
||||
* Version: $Id$
|
||||
*/
|
||||
|
||||
#ifndef _INCLUDE_SM_PGSQL_DATABASE_H_
|
||||
#define _INCLUDE_SM_PGSQL_DATABASE_H_
|
||||
|
||||
#include <amtl/am-refcounting-threadsafe.h>
|
||||
#include <mutex>
|
||||
#include "PgDriver.h"
|
||||
|
||||
class PgQuery;
|
||||
class PgStatement;
|
||||
|
||||
class PgDatabase
|
||||
: public IDatabase,
|
||||
public ke::RefcountedThreadsafe<PgDatabase>
|
||||
{
|
||||
friend class PgQuery;
|
||||
friend class PgStatement;
|
||||
public:
|
||||
PgDatabase(PGconn *pgsql, const DatabaseInfo *info, bool persistent);
|
||||
~PgDatabase();
|
||||
public: //IDatabase
|
||||
bool Close();
|
||||
const char *GetError(int *errorCode=NULL);
|
||||
bool DoSimpleQuery(const char *query);
|
||||
IQuery *DoQuery(const char *query);
|
||||
IPreparedQuery *PrepareQuery(const char *query, char *error, size_t maxlength, int *errCode=NULL);
|
||||
bool QuoteString(const char *str, char buffer[], size_t maxlen, size_t *newSize);
|
||||
unsigned int GetAffectedRows();
|
||||
unsigned int GetInsertID();
|
||||
bool LockForFullAtomicOperation();
|
||||
void UnlockFromFullAtomicOperation();
|
||||
void IncReferenceCount();
|
||||
IDBDriver *GetDriver();
|
||||
bool DoSimpleQueryEx(const char *query, size_t len);
|
||||
IQuery *DoQueryEx(const char *query, size_t len);
|
||||
unsigned int GetAffectedRowsForQuery(IQuery *query);
|
||||
unsigned int GetInsertIDForQuery(IQuery *query);
|
||||
bool SetCharacterSet(const char *characterset);
|
||||
public:
|
||||
const DatabaseInfo &GetInfo();
|
||||
void SetLastIDAndRows(unsigned int insertID, unsigned int affectedRows);
|
||||
private:
|
||||
PGconn *m_pgsql;
|
||||
std::mutex m_FullLock;
|
||||
|
||||
unsigned int m_lastInsertID;
|
||||
unsigned int m_lastAffectedRows;
|
||||
std::mutex m_LastQueryInfoLock;
|
||||
|
||||
unsigned int m_preparedStatementID;
|
||||
|
||||
/* ---------- */
|
||||
DatabaseInfo m_Info;
|
||||
String m_Host;
|
||||
String m_Database;
|
||||
String m_User;
|
||||
String m_Pass;
|
||||
bool m_bPersistent;
|
||||
};
|
||||
|
||||
DBType GetOurType(Oid type);
|
||||
|
||||
#endif //_INCLUDE_SM_PGSQL_DATABASE_H_
|
||||
@@ -0,0 +1,238 @@
|
||||
/**
|
||||
* vim: set ts=4 :
|
||||
* =============================================================================
|
||||
* SourceMod PostgreSQL Extension
|
||||
* Copyright (C) 2013 AlliedModders LLC. 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, version 3.0, as published by the
|
||||
* Free Software Foundation.
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* As a special exception, AlliedModders LLC gives you permission to link the
|
||||
* code of this program (as well as its derivative works) to "Half-Life 2," the
|
||||
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
|
||||
* by the Valve Corporation. You must obey the GNU General Public License in
|
||||
* all respects for all other code used. Additionally, AlliedModders LLC grants
|
||||
* this exception to all derivative works. AlliedModders LLC defines further
|
||||
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
|
||||
* or <http://www.sourcemod.net/license.php>.
|
||||
*
|
||||
* Version: $Id$
|
||||
*/
|
||||
|
||||
#include "PgDriver.h"
|
||||
#include "PgDatabase.h"
|
||||
#include "smsdk_ext.h"
|
||||
|
||||
PgDriver g_PgDriver;
|
||||
|
||||
PgDriver::PgDriver()
|
||||
{
|
||||
m_Handle = BAD_HANDLE;
|
||||
}
|
||||
|
||||
void CloseDBList(List<PgDatabase *> &l)
|
||||
{
|
||||
List<PgDatabase *>::iterator iter;
|
||||
for (iter=l.begin(); iter!=l.end(); iter++)
|
||||
{
|
||||
PgDatabase *db = (*iter);
|
||||
while (!db->Close())
|
||||
{
|
||||
/* Spool until it closes */
|
||||
}
|
||||
}
|
||||
l.clear();
|
||||
}
|
||||
|
||||
void PgDriver::Shutdown()
|
||||
{
|
||||
List<PgDatabase *>::iterator iter;
|
||||
CloseDBList(m_PermDbs);
|
||||
|
||||
if (m_Handle != BAD_HANDLE)
|
||||
{
|
||||
dbi->ReleaseHandle(m_Handle, DBHandle_Driver, myself->GetIdentity());
|
||||
m_Handle = BAD_HANDLE;
|
||||
}
|
||||
}
|
||||
|
||||
const char *PgDriver::GetIdentifier()
|
||||
{
|
||||
return "pgsql";
|
||||
}
|
||||
|
||||
Handle_t PgDriver::GetHandle()
|
||||
{
|
||||
if (m_Handle == BAD_HANDLE)
|
||||
{
|
||||
m_Handle = dbi->CreateHandle(DBHandle_Driver, this, myself->GetIdentity());
|
||||
}
|
||||
|
||||
return m_Handle;
|
||||
}
|
||||
|
||||
IdentityToken_t *PgDriver::GetIdentity()
|
||||
{
|
||||
return myself->GetIdentity();
|
||||
}
|
||||
|
||||
const char *PgDriver::GetProductName()
|
||||
{
|
||||
return "PostgreSQL";
|
||||
}
|
||||
|
||||
PGconn *Connect(const DatabaseInfo *info, char *error, size_t maxlength)
|
||||
{
|
||||
/* https://www.postgresql.org/docs/9.6/libpq-connect.html#LIBPQ-CONNSTRING */
|
||||
/* TODO: Switch to PQconnectdbParams to prevent escaping issues. */
|
||||
char *options = new char[1024];
|
||||
int offs = snprintf(options, 1024, "host='%s' dbname='%s'", info->host, info->database);
|
||||
|
||||
if (info->port > 0)
|
||||
{
|
||||
offs += snprintf(&options[offs], 1024 - offs, " port=%d", info->port);
|
||||
}
|
||||
|
||||
if (info->user[0] != '\0')
|
||||
{
|
||||
offs += snprintf(&options[offs], 1024 - offs, " user='%s'", info->user);
|
||||
}
|
||||
if (info->pass[0] != '\0')
|
||||
{
|
||||
offs += snprintf(&options[offs], 1024 - offs, " password='%s'", info->pass);
|
||||
}
|
||||
|
||||
if (info->maxTimeout > 0)
|
||||
{
|
||||
offs += snprintf(&options[offs], 1024 - offs, " connect_timeout=%d", info->maxTimeout);
|
||||
}
|
||||
|
||||
/* Make a connection to the database */
|
||||
PGconn *conn = PQconnectdb(options);
|
||||
|
||||
delete [] options;
|
||||
|
||||
/* Check to see that the backend connection was successfully made */
|
||||
if (PQstatus(conn) != CONNECTION_OK)
|
||||
{
|
||||
/* :TODO: expose UTIL_Format from smutil! */
|
||||
snprintf(error, maxlength, "%s", PQerrorMessage(conn));
|
||||
PQfinish(conn);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// :TODO: Check for connection problems everytime we talk to the database server and call PQreset on it, when it fails.
|
||||
// There is no automatic reconnect in libpg.
|
||||
// While we're at that. Save the prepared queries and rerun them when we reconnect!
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
bool CompareField(const char *str1, const char *str2)
|
||||
{
|
||||
if ((str1 == NULL && str2 != NULL)
|
||||
|| (str1 != NULL && str2 == NULL))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (str1 == NULL && str2 == NULL)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return (strcmp(str1, str2) == 0);
|
||||
}
|
||||
|
||||
IDatabase *PgDriver::Connect(const DatabaseInfo *info, bool persistent, char *error, size_t maxlength)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_Lock);
|
||||
|
||||
if (persistent)
|
||||
{
|
||||
/* Try to find a matching persistent connection */
|
||||
List<PgDatabase *>::iterator iter;
|
||||
for (iter=m_PermDbs.begin();
|
||||
iter!=m_PermDbs.end();
|
||||
iter++)
|
||||
{
|
||||
PgDatabase *db = (*iter);
|
||||
const DatabaseInfo &other = db->GetInfo();
|
||||
if (CompareField(info->host, other.host)
|
||||
&& CompareField(info->user, other.user)
|
||||
&& CompareField(info->pass, other.pass)
|
||||
&& CompareField(info->database, other.database)
|
||||
&& (info->port == other.port))
|
||||
{
|
||||
db->IncReferenceCount();
|
||||
return db;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PGconn *pgsql = ::Connect(info, error, maxlength);
|
||||
if (!pgsql)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PgDatabase *db = new PgDatabase(pgsql, info, persistent);
|
||||
|
||||
if (persistent)
|
||||
{
|
||||
m_PermDbs.push_back(db);
|
||||
}
|
||||
|
||||
return db;
|
||||
}
|
||||
|
||||
void PgDriver::RemoveFromList(PgDatabase *pdb, bool persistent)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_Lock);
|
||||
if (persistent)
|
||||
{
|
||||
m_PermDbs.remove(pdb);
|
||||
}
|
||||
}
|
||||
|
||||
bool PgDriver::IsThreadSafe()
|
||||
{
|
||||
return (PQisthreadsafe() != 0);
|
||||
}
|
||||
|
||||
bool PgDriver::InitializeThreadSafety()
|
||||
{
|
||||
return IsThreadSafe();
|
||||
}
|
||||
|
||||
void PgDriver::ShutdownThreadSafety()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned int strncopy(char *dest, const char *src, size_t count)
|
||||
{
|
||||
if (!count)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *start = dest;
|
||||
while ((*src) && (--count))
|
||||
{
|
||||
*dest++ = *src++;
|
||||
}
|
||||
*dest = '\0';
|
||||
|
||||
return (dest - start);
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* vim: set ts=4 :
|
||||
* =============================================================================
|
||||
* SourceMod PostgreSQL Extension
|
||||
* Copyright (C) 2013 AlliedModders LLC. 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, version 3.0, as published by the
|
||||
* Free Software Foundation.
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* As a special exception, AlliedModders LLC gives you permission to link the
|
||||
* code of this program (as well as its derivative works) to "Half-Life 2," the
|
||||
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
|
||||
* by the Valve Corporation. You must obey the GNU General Public License in
|
||||
* all respects for all other code used. Additionally, AlliedModders LLC grants
|
||||
* this exception to all derivative works. AlliedModders LLC defines further
|
||||
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
|
||||
* or <http://www.sourcemod.net/license.php>.
|
||||
*
|
||||
* Version: $Id$
|
||||
*/
|
||||
|
||||
#ifndef _INCLUDE_SM_PGSQL_DRIVER_H_
|
||||
#define _INCLUDE_SM_PGSQL_DRIVER_H_
|
||||
|
||||
#define SOURCEMOD_SQL_DRIVER_CODE
|
||||
#include <IDBDriver.h>
|
||||
#include <sm_platform.h>
|
||||
#if defined PLATFORM_WINDOWS
|
||||
#include <winsock.h>
|
||||
#endif
|
||||
|
||||
#include <libpq-fe.h>
|
||||
|
||||
#include <sh_string.h>
|
||||
#include <sh_list.h>
|
||||
|
||||
#include <mutex>
|
||||
|
||||
using namespace SourceMod;
|
||||
using namespace SourceHook;
|
||||
|
||||
#define M_CLIENT_MULTI_RESULTS ((1) << 17) /* Enable/disable multi-results */
|
||||
|
||||
class PgDatabase;
|
||||
|
||||
class PgDriver : public IDBDriver
|
||||
{
|
||||
public:
|
||||
PgDriver();
|
||||
public: //IDBDriver
|
||||
IDatabase *Connect(const DatabaseInfo *info, bool persistent, char *error, size_t maxlength);
|
||||
const char *GetIdentifier();
|
||||
const char *GetProductName();
|
||||
Handle_t GetHandle();
|
||||
IdentityToken_t *GetIdentity();
|
||||
bool IsThreadSafe();
|
||||
bool InitializeThreadSafety();
|
||||
void ShutdownThreadSafety();
|
||||
public:
|
||||
void Shutdown();
|
||||
void RemoveFromList(PgDatabase *pdb, bool persistent);
|
||||
private:
|
||||
std::mutex m_Lock;
|
||||
Handle_t m_Handle;
|
||||
List<PgDatabase *> m_PermDbs;
|
||||
};
|
||||
|
||||
extern PgDriver g_PgDriver;
|
||||
|
||||
unsigned int strncopy(char *dest, const char *src, size_t count);
|
||||
|
||||
#endif //_INCLUDE_SM_PGSQL_DRIVER_H_
|
||||
@@ -0,0 +1,350 @@
|
||||
/**
|
||||
* vim: set ts=4 :
|
||||
* =============================================================================
|
||||
* SourceMod PostgreSQL Extension
|
||||
* Copyright (C) 2013 AlliedModders LLC. 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, version 3.0, as published by the
|
||||
* Free Software Foundation.
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* As a special exception, AlliedModders LLC gives you permission to link the
|
||||
* code of this program (as well as its derivative works) to "Half-Life 2," the
|
||||
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
|
||||
* by the Valve Corporation. You must obey the GNU General Public License in
|
||||
* all respects for all other code used. Additionally, AlliedModders LLC grants
|
||||
* this exception to all derivative works. AlliedModders LLC defines further
|
||||
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
|
||||
* or <http://www.sourcemod.net/license.php>.
|
||||
*
|
||||
* Version: $Id$
|
||||
*/
|
||||
|
||||
#include "PgStatement.h"
|
||||
|
||||
PgStatement::PgStatement(PgDatabase *db, const char* stmtName)
|
||||
: m_pgsql(db->m_pgsql), m_pParent(db), m_insertID(0), m_affectedRows(0), m_rs(NULL), m_Results(false)
|
||||
{
|
||||
m_stmtName = new char[10];
|
||||
strncopy(m_stmtName, stmtName, 10);
|
||||
|
||||
PGresult *desc = PQdescribePrepared(m_pgsql, m_stmtName);
|
||||
|
||||
// TODO: Proper error handling?
|
||||
if (PQresultStatus(desc) != PGRES_COMMAND_OK)
|
||||
{
|
||||
PQclear(desc);
|
||||
return;
|
||||
}
|
||||
|
||||
m_Params = (unsigned int)PQnparams(desc);
|
||||
|
||||
if (m_Params)
|
||||
{
|
||||
m_pushinfo = (ParamBind *)malloc(sizeof(ParamBind) * m_Params);
|
||||
memset(m_pushinfo, 0, sizeof(ParamBind) * m_Params);
|
||||
} else {
|
||||
m_pushinfo = NULL;
|
||||
}
|
||||
|
||||
m_Results = false;
|
||||
}
|
||||
|
||||
PgStatement::~PgStatement()
|
||||
{
|
||||
/* Free result set structures */
|
||||
if (m_Results)
|
||||
{
|
||||
if (m_rs->m_pRes != NULL)
|
||||
PQclear(m_rs->m_pRes);
|
||||
delete m_rs;
|
||||
}
|
||||
|
||||
/* Free old blobs */
|
||||
for (unsigned int i=0; i<m_Params; i++)
|
||||
{
|
||||
free(m_pushinfo[i].blob);
|
||||
}
|
||||
|
||||
/* Free our allocated arrays */
|
||||
free(m_pushinfo);
|
||||
}
|
||||
|
||||
void PgStatement::Destroy()
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
|
||||
bool PgStatement::FetchMoreResults()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void *PgStatement::CopyBlob(unsigned int param, const void *blobptr, size_t length)
|
||||
{
|
||||
void *copy_ptr = NULL;
|
||||
|
||||
if (m_pushinfo[param].blob != NULL)
|
||||
{
|
||||
if (m_pushinfo[param].length < length)
|
||||
{
|
||||
free(m_pushinfo[param].blob);
|
||||
} else {
|
||||
copy_ptr = m_pushinfo[param].blob;
|
||||
}
|
||||
}
|
||||
|
||||
if (copy_ptr == NULL)
|
||||
{
|
||||
copy_ptr = malloc(length);
|
||||
m_pushinfo[param].blob = copy_ptr;
|
||||
m_pushinfo[param].length = length;
|
||||
}
|
||||
|
||||
memcpy(copy_ptr, blobptr, length);
|
||||
|
||||
return copy_ptr;
|
||||
}
|
||||
|
||||
bool PgStatement::BindParamInt(unsigned int param, int num, bool signd)
|
||||
{
|
||||
if (param >= m_Params)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m_pushinfo[param].data.ival = num;
|
||||
m_pushinfo[param].type = DBType_Integer;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PgStatement::BindParamFloat(unsigned int param, float f)
|
||||
{
|
||||
if (param >= m_Params)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m_pushinfo[param].data.fval = f;
|
||||
m_pushinfo[param].type = DBType_Float;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PgStatement::BindParamString(unsigned int param, const char *text, bool copy)
|
||||
{
|
||||
if (param >= m_Params)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const void *final_ptr;
|
||||
size_t len;
|
||||
|
||||
if (copy)
|
||||
{
|
||||
len = strlen(text);
|
||||
final_ptr = CopyBlob(param, text, len+1);
|
||||
} else {
|
||||
len = strlen(text);
|
||||
final_ptr = text;
|
||||
}
|
||||
|
||||
m_pushinfo[param].blob = (void *)final_ptr;
|
||||
m_pushinfo[param].length = len;
|
||||
m_pushinfo[param].type = DBType_String;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PgStatement::BindParamBlob(unsigned int param, const void *data, size_t length, bool copy)
|
||||
{
|
||||
if (param >= m_Params)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const void *final_ptr;
|
||||
|
||||
if (copy)
|
||||
{
|
||||
final_ptr = CopyBlob(param, data, length);
|
||||
} else {
|
||||
final_ptr = data;
|
||||
}
|
||||
|
||||
m_pushinfo[param].blob = (void *)final_ptr;
|
||||
m_pushinfo[param].length = length;
|
||||
m_pushinfo[param].type = DBType_Blob;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PgStatement::BindParamNull(unsigned int param)
|
||||
{
|
||||
if (param >= m_Params)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m_pushinfo[param].type = DBType_NULL;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PgStatement::Execute()
|
||||
{
|
||||
/* Clear any past result first! */
|
||||
if (m_Results)
|
||||
delete m_rs;
|
||||
m_Results = false;
|
||||
|
||||
PGresult *res;
|
||||
/* Bind the parameters */
|
||||
if (m_Params)
|
||||
{
|
||||
// Put bound params into a nice array
|
||||
const char **paramValues = new const char*[m_Params];
|
||||
int *paramLengths = new int[m_Params];
|
||||
int *paramFormats = new int[m_Params];
|
||||
|
||||
for (unsigned int i=0; i<m_Params; i++)
|
||||
{
|
||||
switch (m_pushinfo[i].type)
|
||||
{
|
||||
case DBType_Integer:
|
||||
{
|
||||
char *buf = new char[64]; // 64 chars should be enough?
|
||||
snprintf(buf, 64, "%d", m_pushinfo[i].data.ival);
|
||||
paramValues[i] = buf;
|
||||
paramLengths[i] = 0;
|
||||
paramFormats[i] = 0;
|
||||
break;
|
||||
}
|
||||
case DBType_Float:
|
||||
{
|
||||
char *buf = new char[64]; // 64 chars should be enough?
|
||||
snprintf(buf, 64, "%f", m_pushinfo[i].data.fval);
|
||||
paramValues[i] = buf;
|
||||
paramLengths[i] = 0;
|
||||
paramFormats[i] = 0;
|
||||
break;
|
||||
}
|
||||
case DBType_String:
|
||||
{
|
||||
paramValues[i] = (char *)m_pushinfo[i].blob;
|
||||
paramLengths[i] = m_pushinfo[i].length;
|
||||
paramFormats[i] = 0;
|
||||
break;
|
||||
}
|
||||
case DBType_Blob:
|
||||
{
|
||||
paramValues[i] = (char *)m_pushinfo[i].blob;
|
||||
paramLengths[i] = m_pushinfo[i].length;
|
||||
paramFormats[i] = 1;
|
||||
break;
|
||||
}
|
||||
case DBType_NULL:
|
||||
default:
|
||||
{
|
||||
paramValues[i] = NULL;
|
||||
paramLengths[i] = 0;
|
||||
paramFormats[i] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res = PQexecPrepared(m_pgsql, m_stmtName, m_Params, paramValues, paramLengths, paramFormats, 0);
|
||||
delete [] paramFormats;
|
||||
delete [] paramLengths;
|
||||
|
||||
// .. need to free our char buffers
|
||||
for (unsigned int i=0; i<m_Params; i++)
|
||||
{
|
||||
switch (m_pushinfo[i].type)
|
||||
{
|
||||
case DBType_Integer:
|
||||
case DBType_Float:
|
||||
{
|
||||
delete paramValues[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
delete [] paramValues;
|
||||
}
|
||||
// There are no parameters to be bound!
|
||||
else
|
||||
{
|
||||
res = PQexecPrepared(m_pgsql, m_stmtName, 0, NULL, NULL, NULL, 0);
|
||||
}
|
||||
|
||||
ExecStatusType status = PQresultStatus(res);
|
||||
if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK)
|
||||
{
|
||||
PQclear(res);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_affectedRows = (unsigned int)atoi(PQcmdTuples(res));
|
||||
m_insertID = (unsigned int)PQoidValue(res);
|
||||
|
||||
m_pParent->SetLastIDAndRows(m_insertID, m_affectedRows);
|
||||
|
||||
/* Skip away if we don't have data */
|
||||
if (status == PGRES_COMMAND_OK)
|
||||
{
|
||||
PQclear(res);
|
||||
return true;
|
||||
}
|
||||
|
||||
m_rs = new PgBasicResults(res);
|
||||
m_Results = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const char *PgStatement::GetError(int *errCode/* =NULL */)
|
||||
{
|
||||
if (m_Results)
|
||||
{
|
||||
if (errCode)
|
||||
{
|
||||
// PostgreSQL only supports SQLSTATE error codes.
|
||||
// https://www.postgresql.org/docs/9.6/errcodes-appendix.html
|
||||
*errCode = -1;
|
||||
}
|
||||
|
||||
return PQresultErrorMessage(m_rs->m_pRes);
|
||||
}
|
||||
else
|
||||
{
|
||||
return PQerrorMessage(m_pgsql);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int PgStatement::GetAffectedRows()
|
||||
{
|
||||
return m_affectedRows;
|
||||
}
|
||||
|
||||
unsigned int PgStatement::GetInsertID()
|
||||
{
|
||||
return m_insertID;
|
||||
}
|
||||
|
||||
IResultSet *PgStatement::GetResultSet()
|
||||
{
|
||||
return (m_Results ? m_rs : NULL);
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* vim: set ts=4 :
|
||||
* =============================================================================
|
||||
* SourceMod PostgreSQL Extension
|
||||
* Copyright (C) 2013 AlliedModders LLC. 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, version 3.0, as published by the
|
||||
* Free Software Foundation.
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* As a special exception, AlliedModders LLC gives you permission to link the
|
||||
* code of this program (as well as its derivative works) to "Half-Life 2," the
|
||||
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
|
||||
* by the Valve Corporation. You must obey the GNU General Public License in
|
||||
* all respects for all other code used. Additionally, AlliedModders LLC grants
|
||||
* this exception to all derivative works. AlliedModders LLC defines further
|
||||
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
|
||||
* or <http://www.sourcemod.net/license.php>.
|
||||
*
|
||||
* Version: $Id$
|
||||
*/
|
||||
|
||||
#ifndef _INCLUDE_SM_PGSQL_STATEMENT_H_
|
||||
#define _INCLUDE_SM_PGSQL_STATEMENT_H_
|
||||
|
||||
#include "PgDatabase.h"
|
||||
#include "PgBasicResults.h"
|
||||
|
||||
struct ParamBind
|
||||
{
|
||||
union
|
||||
{
|
||||
float fval;
|
||||
int ival;
|
||||
} data;
|
||||
void *blob;
|
||||
size_t length;
|
||||
DBType type;
|
||||
};
|
||||
|
||||
class PgStatement : public IPreparedQuery
|
||||
{
|
||||
public:
|
||||
PgStatement(PgDatabase *db, const char* stmtName);
|
||||
~PgStatement();
|
||||
public: //IQuery
|
||||
IResultSet *GetResultSet();
|
||||
bool FetchMoreResults();
|
||||
void Destroy();
|
||||
public: //IPreparedQuery
|
||||
bool BindParamInt(unsigned int param, int num, bool signd=true);
|
||||
bool BindParamFloat(unsigned int param, float f);
|
||||
bool BindParamNull(unsigned int param);
|
||||
bool BindParamString(unsigned int param, const char *text, bool copy);
|
||||
bool BindParamBlob(unsigned int param, const void *data, size_t length, bool copy);
|
||||
bool Execute();
|
||||
const char *GetError(int *errCode=NULL);
|
||||
unsigned int GetAffectedRows();
|
||||
unsigned int GetInsertID();
|
||||
private:
|
||||
void *CopyBlob(unsigned int param, const void *blobptr, size_t length);
|
||||
private:
|
||||
PGconn *m_pgsql;
|
||||
ke::RefPtr<PgDatabase> m_pParent;
|
||||
char *m_stmtName;
|
||||
|
||||
ParamBind *m_pushinfo;
|
||||
unsigned int m_Params;
|
||||
|
||||
unsigned int m_insertID;
|
||||
unsigned int m_affectedRows;
|
||||
PgBasicResults *m_rs;
|
||||
bool m_Results;
|
||||
};
|
||||
|
||||
#endif //_INCLUDE_SM_PGSQL_STATEMENT_H_
|
||||
Reference in New Issue
Block a user