/** * 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 . * * 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 . * * 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 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 lock(m_LastQueryInfoLock); return m_lastInsertID; } unsigned int PgDatabase::GetAffectedRows() { std::lock_guard 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(query)->GetAffectedRows(); } unsigned int PgDatabase::GetInsertIDForQuery(IQuery *query) { return static_cast(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; }