122 changes: 122 additions & 0 deletions extensions/pgsql/extension.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/**
* 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_SOURCEMOD_EXTENSION_PROPER_H_
#define _INCLUDE_SOURCEMOD_EXTENSION_PROPER_H_

/**
* @file extension.h
* @brief Sample extension code header.
*/

#define SOURCEMOD_SQL_DRIVER_CODE
#include "smsdk_ext.h"


/**
* @brief Sample implementation of the SDK Extension.
* Note: Uncomment one of the pre-defined virtual functions in order to use it.
*/
class DBI_PgSQL : public SDKExtension
{
public:
/**
* @brief This is called after the initial loading sequence has been processed.
*
* @param error Error message buffer.
* @param maxlength Size of error message buffer.
* @param late Whether or not the module was loaded after map load.
* @return True to succeed loading, false to fail.
*/
virtual bool SDK_OnLoad(char *error, size_t maxlength, bool late);

/**
* @brief This is called right before the extension is unloaded.
*/
virtual void SDK_OnUnload();

/**
* @brief This is called once all known extensions have been loaded.
* Note: It is is a good idea to add natives here, if any are provided.
*/
//virtual void SDK_OnAllLoaded();

/**
* @brief Called when the pause state is changed.
*/
//virtual void SDK_OnPauseChange(bool paused);

/**
* @brief this is called when Core wants to know if your extension is working.
*
* @param error Error message buffer.
* @param maxlength Size of error message buffer.
* @return True if working, false otherwise.
*/
//virtual bool QueryRunning(char *error, size_t maxlength);
const char *GetExtensionVerString();
const char *GetExtensionDateString();
public:
#if defined SMEXT_CONF_METAMOD
/**
* @brief Called when Metamod is attached, before the extension version is called.
*
* @param error Error buffer.
* @param maxlength Maximum size of error buffer.
* @param late Whether or not Metamod considers this a late load.
* @return True to succeed, false to fail.
*/
//virtual bool SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxlength, bool late);

/**
* @brief Called when Metamod is detaching, after the extension version is called.
* NOTE: By default this is blocked unless sent from SourceMod.
*
* @param error Error buffer.
* @param maxlength Maximum size of error buffer.
* @return True to succeed, false to fail.
*/
//virtual bool SDK_OnMetamodUnload(char *error, size_t maxlength);

/**
* @brief Called when Metamod's pause state is changing.
* NOTE: By default this is blocked unless sent from SourceMod.
*
* @param paused Pause state being set.
* @param error Error buffer.
* @param maxlength Maximum size of error buffer.
* @return True to succeed, false to fail.
*/
//virtual bool SDK_OnMetamodPauseChange(bool paused, char *error, size_t maxlength);
#endif
};

#endif // _INCLUDE_SOURCEMOD_EXTENSION_PROPER_H_
Binary file added extensions/pgsql/lib_darwin/libpq.a
Binary file not shown.
8 changes: 8 additions & 0 deletions extensions/pgsql/lib_darwin/pg_config_ext.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/* src/include/pg_config_ext.h. Generated from pg_config_ext.h.in by configure. */
/*
* src/include/pg_config_ext.h.in. This is generated manually, not by
* autoheader, since we want to limit which symbols get defined here.
*/

/* Define to the name of a signed 64-bit integer type. */
#define PG_INT64_TYPE long long int
Binary file added extensions/pgsql/lib_darwin64/libpq.a
Binary file not shown.
8 changes: 8 additions & 0 deletions extensions/pgsql/lib_darwin64/pg_config_ext.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/* src/include/pg_config_ext.h. Generated from pg_config_ext.h.in by configure. */
/*
* src/include/pg_config_ext.h.in. This is generated manually, not by
* autoheader, since we want to limit which symbols get defined here.
*/

/* Define to the name of a signed 64-bit integer type. */
#define PG_INT64_TYPE long int
Binary file added extensions/pgsql/lib_linux/libpq.a
Binary file not shown.
8 changes: 8 additions & 0 deletions extensions/pgsql/lib_linux/pg_config_ext.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/* src/include/pg_config_ext.h. Generated from pg_config_ext.h.in by configure. */
/*
* src/include/pg_config_ext.h.in. This is generated manually, not by
* autoheader, since we want to limit which symbols get defined here.
*/

/* Define to the name of a signed 64-bit integer type. */
#define PG_INT64_TYPE long long int
Binary file added extensions/pgsql/lib_linux64/libpq.a
Binary file not shown.
8 changes: 8 additions & 0 deletions extensions/pgsql/lib_linux64/pg_config_ext.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/* src/include/pg_config_ext.h. Generated from pg_config_ext.h.in by configure. */
/*
* src/include/pg_config_ext.h.in. This is generated manually, not by
* autoheader, since we want to limit which symbols get defined here.
*/

/* Define to the name of a signed 64-bit integer type. */
#define PG_INT64_TYPE long int
Binary file added extensions/pgsql/lib_win/libpq.lib
Binary file not shown.
7 changes: 7 additions & 0 deletions extensions/pgsql/lib_win/pg_config_ext.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* src/include/pg_config_ext.h.win32. This is generated manually, not by
* autoheader, since we want to limit which symbols get defined here.
*/

/* Define to the name of a signed 64-bit integer type. */
#define PG_INT64_TYPE long long int
Binary file added extensions/pgsql/lib_win64/libpq.lib
Binary file not shown.
7 changes: 7 additions & 0 deletions extensions/pgsql/lib_win64/pg_config_ext.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* src/include/pg_config_ext.h.win32. This is generated manually, not by
* autoheader, since we want to limit which symbols get defined here.
*/

/* Define to the name of a signed 64-bit integer type. */
#define PG_INT64_TYPE long long int
607 changes: 607 additions & 0 deletions extensions/pgsql/libpq-fe.h

Large diffs are not rendered by default.

350 changes: 350 additions & 0 deletions extensions/pgsql/pgsql/PgBasicResults.cpp
Original file line number Diff line number Diff line change
@@ -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;
}
102 changes: 102 additions & 0 deletions extensions/pgsql/pgsql/PgBasicResults.h
Original file line number Diff line number Diff line change
@@ -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_
346 changes: 346 additions & 0 deletions extensions/pgsql/pgsql/PgDatabase.cpp
Original file line number Diff line number Diff line change
@@ -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;
}
93 changes: 93 additions & 0 deletions extensions/pgsql/pgsql/PgDatabase.h
Original file line number Diff line number Diff line change
@@ -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_
238 changes: 238 additions & 0 deletions extensions/pgsql/pgsql/PgDriver.cpp
Original file line number Diff line number Diff line change
@@ -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];
peace-maker marked this conversation as resolved.
Show resolved Hide resolved
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);
}
82 changes: 82 additions & 0 deletions extensions/pgsql/pgsql/PgDriver.h
Original file line number Diff line number Diff line change
@@ -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_
350 changes: 350 additions & 0 deletions extensions/pgsql/pgsql/PgStatement.cpp
Original file line number Diff line number Diff line change
@@ -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);
}
85 changes: 85 additions & 0 deletions extensions/pgsql/pgsql/PgStatement.h
Original file line number Diff line number Diff line change
@@ -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_
70 changes: 70 additions & 0 deletions extensions/pgsql/postgres_ext.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*-------------------------------------------------------------------------
*
* postgres_ext.h
*
* This file contains declarations of things that are visible everywhere
* in PostgreSQL *and* are visible to clients of frontend interface libraries.
* For example, the Oid type is part of the API of libpq and other libraries.
*
* Declarations which are specific to a particular interface should
* go in the header file for that interface (such as libpq-fe.h). This
* file is only for fundamental Postgres declarations.
*
* User-written C functions don't count as "external to Postgres."
* Those function much as local modifications to the backend itself, and
* use header files that are otherwise internal to Postgres to interface
* with the backend.
*
* src/include/postgres_ext.h
*
*-------------------------------------------------------------------------
*/

#ifndef POSTGRES_EXT_H
#define POSTGRES_EXT_H

#include "pg_config_ext.h"

/*
* Object ID is a fundamental type in Postgres.
*/
typedef unsigned int Oid;

#ifdef __cplusplus
#define InvalidOid (Oid(0))
#else
#define InvalidOid ((Oid) 0)
#endif

#define OID_MAX UINT_MAX
/* you will need to include <limits.h> to use the above #define */

/* Define a signed 64-bit integer type for use in client API declarations. */
typedef PG_INT64_TYPE pg_int64;


/*
* Identifiers of error message fields. Kept here to keep common
* between frontend and backend, and also to export them to libpq
* applications.
*/
#define PG_DIAG_SEVERITY 'S'
#define PG_DIAG_SEVERITY_NONLOCALIZED 'V'
#define PG_DIAG_SQLSTATE 'C'
#define PG_DIAG_MESSAGE_PRIMARY 'M'
#define PG_DIAG_MESSAGE_DETAIL 'D'
#define PG_DIAG_MESSAGE_HINT 'H'
#define PG_DIAG_STATEMENT_POSITION 'P'
#define PG_DIAG_INTERNAL_POSITION 'p'
#define PG_DIAG_INTERNAL_QUERY 'q'
#define PG_DIAG_CONTEXT 'W'
#define PG_DIAG_SCHEMA_NAME 's'
#define PG_DIAG_TABLE_NAME 't'
#define PG_DIAG_COLUMN_NAME 'c'
#define PG_DIAG_DATATYPE_NAME 'd'
#define PG_DIAG_CONSTRAINT_NAME 'n'
#define PG_DIAG_SOURCE_FILE 'F'
#define PG_DIAG_SOURCE_LINE 'L'
#define PG_DIAG_SOURCE_FUNCTION 'R'

#endif /* POSTGRES_EXT_H */
72 changes: 72 additions & 0 deletions extensions/pgsql/smsdk_config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* 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_SOURCEMOD_EXTENSION_CONFIG_H_
#define _INCLUDE_SOURCEMOD_EXTENSION_CONFIG_H_

/**
* @file smsdk_config.h
* @brief Contains macros for configuring basic extension information.
*/

/* Basic information exposed publicly */
#define SMEXT_CONF_NAME "PostgreSQL-DBI"
#define SMEXT_CONF_DESCRIPTION "PostgreSQL driver implementation for DBI"
#define SMEXT_CONF_VERSION ""
#define SMEXT_CONF_AUTHOR "AlliedModders LLC"
#define SMEXT_CONF_URL "http://www.sourcemod.net/"
#define SMEXT_CONF_LOGTAG "PGSQL"
#define SMEXT_CONF_LICENSE "GPL"
#define SMEXT_CONF_DATESTRING ""

/**
* @brief Exposes plugin's main interface.
*/
#define SMEXT_LINK(name) SDKExtension *g_pExtensionIface = name;

/**
* @brief Sets whether or not this plugin required Metamod.
* NOTE: Uncomment to enable, comment to disable.
*/
//#define SMEXT_CONF_METAMOD

/** Enable interfaces you want to use here by uncommenting lines */
//#define SMEXT_ENABLE_FORWARDSYS
//#define SMEXT_ENABLE_HANDLESYS
//#define SMEXT_ENABLE_PLAYERHELPERS
#define SMEXT_ENABLE_DBMANAGER
//#define SMEXT_ENABLE_GAMECONF
//#define SMEXT_ENABLE_MEMUTILS
//#define SMEXT_ENABLE_GAMEHELPERS
//#define SMEXT_ENABLE_TIMERSYS
//#define SMEXT_ENABLE_THREADER

#endif // _INCLUDE_SOURCEMOD_EXTENSION_CONFIG_H_
104 changes: 104 additions & 0 deletions extensions/pgsql/version.rc
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Microsoft Visual C++ generated resource script.
//
//#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"

#include <sourcemod_version.h>

/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS

/////////////////////////////////////////////////////////////////////////////
// English (U.S.) resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
#ifdef _WIN32
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
#endif //_WIN32

/////////////////////////////////////////////////////////////////////////////
//
// Version
//

VS_VERSION_INFO VERSIONINFO
FILEVERSION SM_VERSION_FILE
PRODUCTVERSION SM_VERSION_FILE
FILEFLAGSMASK 0x17L
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x4L
FILETYPE 0x2L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "000004b0"
BEGIN
VALUE "Comments", "PostgreSQL Extension"
VALUE "FileDescription", "SourceMod PostgreSQL Extension"
VALUE "FileVersion", SM_VERSION_STRING
VALUE "InternalName", "SourceMod PostgreSQL Extension"
VALUE "LegalCopyright", "Copyright (c) 2004-2013, AlliedModders LLC"
VALUE "OriginalFilename", BINARY_NAME
VALUE "ProductName", "SourceMod PostgreSQL Extension"
VALUE "ProductVersion", SM_VERSION_STRING
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x0, 1200
END
END


#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//

1 TEXTINCLUDE
BEGIN
"resource.h\0"
END

2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END

3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END

#endif // APSTUDIO_INVOKED

#endif // English (U.S.) resources
/////////////////////////////////////////////////////////////////////////////



#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//


/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED

67 changes: 67 additions & 0 deletions plugins/sql-admin-manager.sp
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,46 @@ void CreateSQLite(int client, Database db)
ReplyToCommand(client, "[SM] Admin tables have been created.");
}

void CreatePgSQL(int client, Database db)
{
char queries[7][] =
{
"CREATE TABLE sm_admins (id serial, authtype varchar(6) NOT NULL, CHECK (authtype in ('steam', 'name', 'ip')), identity varchar(65) NOT NULL, password varchar(65), flags varchar(30) NOT NULL, name varchar(65) NOT NULL, immunity int NOT NULL, PRIMARY KEY (id))",
"CREATE TABLE sm_groups (id serial, flags varchar(30) NOT NULL, name varchar(120) NOT NULL, immunity_level int NOT NULL, PRIMARY KEY (id))",
"CREATE TABLE sm_group_immunity (group_id int NOT NULL, other_id int NOT NULL, FOREIGN KEY (group_id) REFERENCES sm_groups(id) ON DELETE CASCADE, FOREIGN KEY (other_id) REFERENCES sm_groups(id) ON DELETE CASCADE, PRIMARY KEY (group_id, other_id))",
"CREATE TABLE sm_group_overrides (group_id int NOT NULL, FOREIGN KEY (group_id) REFERENCES sm_groups(id) ON DELETE CASCADE, type varchar(10) NOT NULL, CHECK (type in ('command', 'group')), name varchar(32) NOT NULL, access varchar(5) NOT NULL, CHECK (access in ('allow', 'deny')), PRIMARY KEY (group_id, type, name))",
"CREATE TABLE sm_overrides (type varchar(10) NOT NULL, CHECK (type in ('command', 'group')), name varchar(32) NOT NULL, flags varchar(30) NOT NULL, PRIMARY KEY (type,name))",
"CREATE TABLE sm_admins_groups (admin_id int NOT NULL, group_id int NOT NULL, FOREIGN KEY (admin_id) REFERENCES sm_admins(id) ON DELETE CASCADE, FOREIGN KEY (group_id) REFERENCES sm_groups(id) ON DELETE CASCADE, inherit_order int NOT NULL, PRIMARY KEY (admin_id, group_id))",
"CREATE TABLE sm_config (cfg_key varchar(32) NOT NULL, cfg_value varchar(255) NOT NULL, PRIMARY KEY (cfg_key))"
};

for (int i = 0; i < 7; i++)
{
if (!DoQuery(client, db, queries[i]))
{
return;
}
}

char query[256];
Format(query,
sizeof(query),
"INSERT INTO sm_config (cfg_key, cfg_value) VALUES ('admin_version', '1.0.0.%d')",
CURRENT_SCHEMA_VERSION);

if (!SQL_FastQuery(db, query))
{
Format(query,
sizeof(query),
"UPDATE sm_config SET cfg_value = '1.0.0.%d' WHERE cfg_key = 'admin_version'",
CURRENT_SCHEMA_VERSION);
if (!DoQuery(client, db, query))
return;
}

ReplyToCommand(client, "[SM] Admin tables have been created.");
}

public Action Command_CreateTables(int args)
{
int client = 0;
Expand All @@ -174,6 +214,8 @@ public Action Command_CreateTables(int args)
CreateMySQL(client, db);
} else if (strcmp(ident, "sqlite") == 0) {
CreateSQLite(client, db);
} else if (strcmp(ident, "pgsql") == 0) {
CreatePgSQL(client, db);
} else {
ReplyToCommand(client, "[SM] Unknown driver type '%s', cannot create tables.", ident);
}
Expand Down Expand Up @@ -361,6 +403,29 @@ void UpdateMySQL(int client, Database db)
ReplyToCommand(client, "[SM] Your tables are now up to date.");
}

void UpdatePgSQL(int client, Database db)
{
// PostgreSQL support was added way after there was something to update the tables from.
// The correct schemas are created right away.

int versions[4];

if (!GetUpdateVersion(client, db, versions)) // Partly just here to keep the compiler from complaining about unused parameters ;)
{
return;
}

/* We only know about one upgrade path right now...
* 0 => 1
*/
if (versions[3] < SCHEMA_UPGRADE_1)
{
// Nope..
}

ReplyToCommand(client, "[SM] Your tables are now up to date.");
}

public Action Command_UpdateTables(int args)
{
int client = 0;
Expand All @@ -379,6 +444,8 @@ public Action Command_UpdateTables(int args)
UpdateMySQL(client, db);
} else if (strcmp(ident, "sqlite") == 0) {
UpdateSQLite(client, db);
} else if (strcmp(ident, "pgsql") == 0) {
UpdatePgSQL(client, db);
} else {
ReplyToCommand(client, "[SM] Unknown driver type, cannot upgrade.");
}
Expand Down