diff --git a/AMBuildScript b/AMBuildScript index 0e462eab..2e1224bf 100644 --- a/AMBuildScript +++ b/AMBuildScript @@ -729,6 +729,7 @@ BuildScripts = [ 'extensions/cstrike/AMBuilder', 'extensions/geoip/AMBuilder', 'extensions/mysql/AMBuilder', + 'extensions/pgsql/AMBuilder', 'extensions/regex/AMBuilder', 'extensions/sdkhooks/AMBuilder', 'extensions/sdktools/AMBuilder', diff --git a/configs/sql-init-scripts/pgsql/clientprefs-pgsql.sql b/configs/sql-init-scripts/pgsql/clientprefs-pgsql.sql new file mode 100644 index 00000000..f7946344 --- /dev/null +++ b/configs/sql-init-scripts/pgsql/clientprefs-pgsql.sql @@ -0,0 +1,41 @@ +CREATE TABLE IF NOT EXISTS sm_cookies +( + id serial, + name varchar(30) NOT NULL UNIQUE, + description varchar(255), + access INTEGER, + PRIMARY KEY (id) +); + +CREATE TABLE IF NOT EXISTS sm_cookie_cache +( + player varchar(65) NOT NULL, + cookie_id int NOT NULL, + value varchar(100), + timestamp int NOT NULL, + PRIMARY KEY (player, cookie_id) +); + +CREATE LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION add_or_update_cookie(in_player VARCHAR(65), in_cookie INT, in_value VARCHAR(100), in_time INT) RETURNS VOID AS +$$ +BEGIN + LOOP + -- first try to update the it. + UPDATE sm_cookie_cache SET value = in_value, timestamp = in_time WHERE player = in_player AND cookie_id = in_cookie; + IF found THEN + RETURN; + END IF; + -- not there, so try to insert. + -- if someone else inserts the same key concurrently, we could get a unique-key failure. + BEGIN + INSERT INTO sm_cookie_cache (player, cookie_id, value, timestamp) VALUES (in_player, in_cookie, in_value, in_time); + RETURN; + EXCEPTION WHEN unique_violation THEN + -- do nothing... loop again, and we'll update. + END; + END LOOP; +END; +$$ +LANGUAGE plpgsql; \ No newline at end of file diff --git a/configs/sql-init-scripts/pgsql/create_admins.sql b/configs/sql-init-scripts/pgsql/create_admins.sql new file mode 100644 index 00000000..508e8f66 --- /dev/null +++ b/configs/sql-init-scripts/pgsql/create_admins.sql @@ -0,0 +1,65 @@ +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) +); + +-- side note, this is pgsql module, sm_config will not exist if the above stuff exists... and it's being left to the admin +-- to figure out if it exists. +CREATE TABLE sm_config ( + cfg_key varchar(32) NOT NULL, + cfg_value varchar(255) NOT NULL, + PRIMARY KEY (cfg_key) +); + +INSERT INTO sm_config (cfg_key, cfg_value) VALUES ('admin_version', '1.0.0.1409'); \ No newline at end of file diff --git a/extensions/clientprefs/extension.cpp b/extensions/clientprefs/extension.cpp index 7f1d405e..12c24ae7 100644 --- a/extensions/clientprefs/extension.cpp +++ b/extensions/clientprefs/extension.cpp @@ -287,6 +287,74 @@ void ClientPrefs::DatabaseConnect() goto fatal_fail; } } + else if (strcmp(identifier, "pgsql") == 0) + { + g_DriverType = Driver_PgSQL; + // PostgreSQL supports 'IF NOT EXISTS' as of 9.1 + if (!Database->DoSimpleQuery( + "CREATE TABLE IF NOT EXISTS sm_cookies \ + ( \ + id serial, \ + name varchar(30) NOT NULL UNIQUE, \ + description varchar(255), \ + access INTEGER, \ + PRIMARY KEY (id) \ + )")) + { + g_pSM->LogMessage(myself, "Failed to CreateTable sm_cookies: %s", Database->GetError()); + goto fatal_fail; + } + + if (!Database->DoSimpleQuery( + "CREATE TABLE IF NOT EXISTS sm_cookie_cache \ + ( \ + player varchar(65) NOT NULL, \ + cookie_id int NOT NULL, \ + value varchar(100), \ + timestamp int NOT NULL, \ + PRIMARY KEY (player, cookie_id) \ + )")) + { + g_pSM->LogMessage(myself, "Failed to CreateTable sm_cookie_cache: %s", Database->GetError()); + goto fatal_fail; + } + + if (!Database->DoSimpleQuery( + "CREATE TABLE IF NOT EXISTS sm_cookie_cache \ + ( \ + player varchar(65) NOT NULL, \ + cookie_id int NOT NULL, \ + value varchar(100), \ + timestamp int NOT NULL, \ + PRIMARY KEY (player, cookie_id) \ + )")) + { + g_pSM->LogMessage(myself, "Failed to CreateTable sm_cookie_cache: %s", Database->GetError()); + goto fatal_fail; + } + + if (!Database->DoSimpleQuery( + "CREATE OR REPLACE FUNCTION add_or_update_cookie(in_player VARCHAR(65), in_cookie INT, in_value VARCHAR(100), in_time INT) RETURNS VOID AS \ + $$ \ + BEGIN \ + LOOP \ + UPDATE sm_cookie_cache SET value = in_value, timestamp = in_time WHERE player = in_player AND cookie_id = in_cookie; \ + IF found THEN \ + RETURN; \ + END IF; \ + BEGIN \ + INSERT INTO sm_cookie_cache (player, cookie_id, value, timestamp) VALUES (in_player, in_cookie, in_value, in_time); \ + RETURN; \ + EXCEPTION WHEN unique_violation THEN \ + END; \ + END LOOP; \ + END; \ + $$ LANGUAGE plpgsql;")) + { + g_pSM->LogMessage(myself, "Failed to create function add_or_update_cookie: %s", Database->GetError()); + goto fatal_fail; + } + } else { g_pSM->LogError(myself, "Unsupported driver \"%s\"", identifier); diff --git a/extensions/clientprefs/extension.h b/extensions/clientprefs/extension.h index b14cc453..333445c5 100644 --- a/extensions/clientprefs/extension.h +++ b/extensions/clientprefs/extension.h @@ -49,7 +49,8 @@ char * UTIL_strncpy(char * destination, const char * source, size_t num); enum DbDriver { Driver_MySQL, - Driver_SQLite + Driver_SQLite, + Driver_PgSQL }; #define MAX_TRANSLATE_PARAMS 32 diff --git a/extensions/clientprefs/query.cpp b/extensions/clientprefs/query.cpp index f1883ee3..a559b0ab 100644 --- a/extensions/clientprefs/query.cpp +++ b/extensions/clientprefs/query.cpp @@ -177,6 +177,17 @@ bool TQueryOp::BindParamsAndRun() safe_desc, m_params.cookie->access); } + else if (g_DriverType == Driver_PgSQL) + { + // just insert. Returns error on already exists, so ignore the error. + g_pSM->Format(query, + sizeof(query), + "INSERT INTO sm_cookies (name, description, access) \ + VALUES ('%s', '%s', %d)", + safe_name, + safe_desc, + m_params.cookie->access); + } if (!m_database->DoSimpleQuery(query)) { @@ -250,6 +261,18 @@ bool TQueryOp::BindParamsAndRun() safe_val, (unsigned int)m_params.data->timestamp); } + else if (g_DriverType == Driver_PgSQL) + { + // Using a PL/Pgsql function, called add_or_update_cookie(), + // since Postgres does not have an 'OR REPLACE' functionality. + g_pSM->Format(query, + sizeof(query), + "SELECT add_or_update_cookie ('%s', %d, '%s', %d)", + safe_id, + m_params.cookieId, + safe_val, + (unsigned int)m_params.data->timestamp); + } if (!m_database->DoSimpleQuery(query)) { diff --git a/extensions/pgsql/AMBuilder b/extensions/pgsql/AMBuilder new file mode 100644 index 00000000..aecbaeaa --- /dev/null +++ b/extensions/pgsql/AMBuilder @@ -0,0 +1,62 @@ +# vim: set sts=2 ts=8 sw=2 tw=99 et ft=python: +import os + +for cxx in builder.targets: + arch = cxx.target.arch + binary = SM.ExtLibrary(builder, cxx, 'dbi.pgsql.ext') + compiler = binary.compiler + compiler.cxxincludes += [ + os.path.join(SM.mms_root, 'core', 'sourcehook') + ] + if compiler.family == 'gcc' or compiler.family == 'clang': + compiler.cxxflags += ['-fno-rtti'] + elif compiler.family == 'msvc': + compiler.cxxflags += ['/GR-'] + + if compiler.target.platform == 'linux' or compiler.target.platform == 'mac': + compiler.postlink += [ + '-lz', + '-lpthread', + '-lm' + ] + if compiler.target.platform == 'linux': + if arch == 'x86': + path = os.path.join(builder.sourcePath, 'extensions', 'pgsql', 'lib_linux') + elif arch == 'x86_64': + path = os.path.join(builder.sourcePath, 'extensions', 'pgsql', 'lib_linux64') + compiler.postlink += [ + os.path.join(path, 'libpq.a'), + '-lrt' + ] + elif compiler.target.platform == 'windows': + if arch == 'x86': + path = os.path.join(builder.sourcePath, 'extensions', 'pgsql', 'lib_win') + elif arch == 'x86_64': + path = os.path.join(builder.sourcePath, 'extensions', 'pgsql', 'lib_win64') + compiler.postlink += [ + os.path.join(path, 'libpq.lib'), + 'wsock32.lib', + 'ws2_32.lib', + 'secur32.lib' + ] + elif compiler.target.platform == 'mac': + if arch == 'x86': + path = os.path.join(builder.sourcePath, 'extensions', 'pgsql', 'lib_darwin') + elif arch == 'x86_64': + path = os.path.join(builder.sourcePath, 'extensions', 'pgsql', 'lib_darwin64') + compiler.postlink += [ + os.path.join(path, 'libpq.a') + ] + + compiler.cxxincludes += [path] + + binary.sources += [ + os.path.join('..', '..', 'public', 'smsdk_ext.cpp'), + os.path.join('pgsql', 'PgBasicResults.cpp'), + os.path.join('pgsql', 'PgDatabase.cpp'), + os.path.join('pgsql', 'PgDriver.cpp'), + os.path.join('pgsql', 'PgStatement.cpp'), + 'extension.cpp' + ] + SM.extensions += [builder.Add(binary)] + diff --git a/extensions/pgsql/README.md b/extensions/pgsql/README.md new file mode 100644 index 00000000..22b17bc0 --- /dev/null +++ b/extensions/pgsql/README.md @@ -0,0 +1,22 @@ +# PostgreSQL Build Instructions +As of writing, we're using PostgreSQL v9.6.9 + +https://www.postgresql.org/ftp/source/ + +After building, libpq can be found in src/interfaces/libpq/ + +## Windows +https://www.postgresql.org/docs/9.6/install-windows-libpq.html + +Change `src/interfaces/win32.mak` line 35 from `OPT=/O2 /MD` to `OPT=/O2 /MT`. Library will be in `interfaces\libpq\Release\libpq.lib`. +You have to delete the interfaces\libpq\Release folder between x86 and x86_64 builds. + +## Mac +For x86 or x86_64 add -m32 or -m64 to `CFLAGS` + +`CFLAGS='-mmacosx-version-min=10.7' ./configure && make` + +## Linux +For x86 or x86_64 add -m32 or -m64 to `CFLAGS` + +`CFLAGS='-fPIC' ./configure --without-readline && make` diff --git a/extensions/pgsql/extension.cpp b/extensions/pgsql/extension.cpp new file mode 100644 index 00000000..a9829f34 --- /dev/null +++ b/extensions/pgsql/extension.cpp @@ -0,0 +1,67 @@ +/** + * 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 +#include "extension.h" +#include "pgsql/PgDriver.h" +#include +#include + +/** + * @file extension.cpp + * @brief Implement extension code here. + */ + +DBI_PgSQL g_PgSqlDBI; /**< Global singleton for extension's main interface */ + +SMEXT_LINK(&g_PgSqlDBI); + +bool DBI_PgSQL::SDK_OnLoad(char *error, size_t maxlength, bool late) +{ + dbi->AddDriver(&g_PgDriver); + + return true; +} + +void DBI_PgSQL::SDK_OnUnload() +{ + dbi->RemoveDriver(&g_PgDriver); +} + +const char *DBI_PgSQL::GetExtensionVerString() +{ + return SOURCEMOD_VERSION; +} + +const char *DBI_PgSQL::GetExtensionDateString() +{ + return SOURCEMOD_BUILD_TIME; +} diff --git a/extensions/pgsql/extension.h b/extensions/pgsql/extension.h new file mode 100644 index 00000000..fc50e739 --- /dev/null +++ b/extensions/pgsql/extension.h @@ -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 . + * + * 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$ + */ + +#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_ diff --git a/extensions/pgsql/lib_darwin/libpq.a b/extensions/pgsql/lib_darwin/libpq.a new file mode 100644 index 00000000..31e21e88 Binary files /dev/null and b/extensions/pgsql/lib_darwin/libpq.a differ diff --git a/extensions/pgsql/lib_darwin/pg_config_ext.h b/extensions/pgsql/lib_darwin/pg_config_ext.h new file mode 100644 index 00000000..f8238576 --- /dev/null +++ b/extensions/pgsql/lib_darwin/pg_config_ext.h @@ -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 diff --git a/extensions/pgsql/lib_darwin64/libpq.a b/extensions/pgsql/lib_darwin64/libpq.a new file mode 100644 index 00000000..f85ce17d Binary files /dev/null and b/extensions/pgsql/lib_darwin64/libpq.a differ diff --git a/extensions/pgsql/lib_darwin64/pg_config_ext.h b/extensions/pgsql/lib_darwin64/pg_config_ext.h new file mode 100644 index 00000000..b4c07dd8 --- /dev/null +++ b/extensions/pgsql/lib_darwin64/pg_config_ext.h @@ -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 diff --git a/extensions/pgsql/lib_linux/libpq.a b/extensions/pgsql/lib_linux/libpq.a new file mode 100644 index 00000000..e1d9b04a Binary files /dev/null and b/extensions/pgsql/lib_linux/libpq.a differ diff --git a/extensions/pgsql/lib_linux/pg_config_ext.h b/extensions/pgsql/lib_linux/pg_config_ext.h new file mode 100644 index 00000000..f8238576 --- /dev/null +++ b/extensions/pgsql/lib_linux/pg_config_ext.h @@ -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 diff --git a/extensions/pgsql/lib_linux64/libpq.a b/extensions/pgsql/lib_linux64/libpq.a new file mode 100644 index 00000000..c60bc577 Binary files /dev/null and b/extensions/pgsql/lib_linux64/libpq.a differ diff --git a/extensions/pgsql/lib_linux64/pg_config_ext.h b/extensions/pgsql/lib_linux64/pg_config_ext.h new file mode 100644 index 00000000..b4c07dd8 --- /dev/null +++ b/extensions/pgsql/lib_linux64/pg_config_ext.h @@ -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 diff --git a/extensions/pgsql/lib_win/libpq.lib b/extensions/pgsql/lib_win/libpq.lib new file mode 100644 index 00000000..171ee65e Binary files /dev/null and b/extensions/pgsql/lib_win/libpq.lib differ diff --git a/extensions/pgsql/lib_win/pg_config_ext.h b/extensions/pgsql/lib_win/pg_config_ext.h new file mode 100644 index 00000000..65bbb5d8 --- /dev/null +++ b/extensions/pgsql/lib_win/pg_config_ext.h @@ -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 diff --git a/extensions/pgsql/lib_win64/libpq.lib b/extensions/pgsql/lib_win64/libpq.lib new file mode 100644 index 00000000..d95a2162 Binary files /dev/null and b/extensions/pgsql/lib_win64/libpq.lib differ diff --git a/extensions/pgsql/lib_win64/pg_config_ext.h b/extensions/pgsql/lib_win64/pg_config_ext.h new file mode 100644 index 00000000..65bbb5d8 --- /dev/null +++ b/extensions/pgsql/lib_win64/pg_config_ext.h @@ -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 diff --git a/extensions/pgsql/libpq-fe.h b/extensions/pgsql/libpq-fe.h new file mode 100644 index 00000000..9ca0756c --- /dev/null +++ b/extensions/pgsql/libpq-fe.h @@ -0,0 +1,607 @@ +/*------------------------------------------------------------------------- + * + * libpq-fe.h + * This file contains definitions for structures and + * externs for functions used by frontend postgres applications. + * + * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/interfaces/libpq/libpq-fe.h + * + *------------------------------------------------------------------------- + */ + +#ifndef LIBPQ_FE_H +#define LIBPQ_FE_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include + +/* + * postgres_ext.h defines the backend's externally visible types, + * such as Oid. + */ +#include "postgres_ext.h" + +/* + * Option flags for PQcopyResult + */ +#define PG_COPYRES_ATTRS 0x01 +#define PG_COPYRES_TUPLES 0x02 /* Implies PG_COPYRES_ATTRS */ +#define PG_COPYRES_EVENTS 0x04 +#define PG_COPYRES_NOTICEHOOKS 0x08 + +/* Application-visible enum types */ + +/* + * Although it is okay to add to these lists, values which become unused + * should never be removed, nor should constants be redefined - that would + * break compatibility with existing code. + */ + +typedef enum +{ + CONNECTION_OK, + CONNECTION_BAD, + /* Non-blocking mode only below here */ + + /* + * The existence of these should never be relied upon - they should only + * be used for user feedback or similar purposes. + */ + CONNECTION_STARTED, /* Waiting for connection to be made. */ + CONNECTION_MADE, /* Connection OK; waiting to send. */ + CONNECTION_AWAITING_RESPONSE, /* Waiting for a response from the + * postmaster. */ + CONNECTION_AUTH_OK, /* Received authentication; waiting for + * backend startup. */ + CONNECTION_SETENV, /* Negotiating environment. */ + CONNECTION_SSL_STARTUP, /* Negotiating SSL. */ + CONNECTION_NEEDED /* Internal state: connect() needed */ +} ConnStatusType; + +typedef enum +{ + PGRES_POLLING_FAILED = 0, + PGRES_POLLING_READING, /* These two indicate that one may */ + PGRES_POLLING_WRITING, /* use select before polling again. */ + PGRES_POLLING_OK, + PGRES_POLLING_ACTIVE /* unused; keep for awhile for backwards + * compatibility */ +} PostgresPollingStatusType; + +typedef enum +{ + PGRES_EMPTY_QUERY = 0, /* empty query string was executed */ + PGRES_COMMAND_OK, /* a query command that doesn't return + * anything was executed properly by the + * backend */ + PGRES_TUPLES_OK, /* a query command that returns tuples was + * executed properly by the backend, PGresult + * contains the result tuples */ + PGRES_COPY_OUT, /* Copy Out data transfer in progress */ + PGRES_COPY_IN, /* Copy In data transfer in progress */ + PGRES_BAD_RESPONSE, /* an unexpected response was recv'd from the + * backend */ + PGRES_NONFATAL_ERROR, /* notice or warning message */ + PGRES_FATAL_ERROR, /* query failed */ + PGRES_COPY_BOTH, /* Copy In/Out data transfer in progress */ + PGRES_SINGLE_TUPLE /* single tuple from larger resultset */ +} ExecStatusType; + +typedef enum +{ + PQTRANS_IDLE, /* connection idle */ + PQTRANS_ACTIVE, /* command in progress */ + PQTRANS_INTRANS, /* idle, within transaction block */ + PQTRANS_INERROR, /* idle, within failed transaction */ + PQTRANS_UNKNOWN /* cannot determine status */ +} PGTransactionStatusType; + +typedef enum +{ + PQERRORS_TERSE, /* single-line error messages */ + PQERRORS_DEFAULT, /* recommended style */ + PQERRORS_VERBOSE /* all the facts, ma'am */ +} PGVerbosity; + +typedef enum +{ + PQSHOW_CONTEXT_NEVER, /* never show CONTEXT field */ + PQSHOW_CONTEXT_ERRORS, /* show CONTEXT for errors only (default) */ + PQSHOW_CONTEXT_ALWAYS /* always show CONTEXT field */ +} PGContextVisibility; + +/* + * PGPing - The ordering of this enum should not be altered because the + * values are exposed externally via pg_isready. + */ + +typedef enum +{ + PQPING_OK, /* server is accepting connections */ + PQPING_REJECT, /* server is alive but rejecting connections */ + PQPING_NO_RESPONSE, /* could not establish connection */ + PQPING_NO_ATTEMPT /* connection not attempted (bad params) */ +} PGPing; + +/* PGconn encapsulates a connection to the backend. + * The contents of this struct are not supposed to be known to applications. + */ +typedef struct pg_conn PGconn; + +/* PGresult encapsulates the result of a query (or more precisely, of a single + * SQL command --- a query string given to PQsendQuery can contain multiple + * commands and thus return multiple PGresult objects). + * The contents of this struct are not supposed to be known to applications. + */ +typedef struct pg_result PGresult; + +/* PGcancel encapsulates the information needed to cancel a running + * query on an existing connection. + * The contents of this struct are not supposed to be known to applications. + */ +typedef struct pg_cancel PGcancel; + +/* PGnotify represents the occurrence of a NOTIFY message. + * Ideally this would be an opaque typedef, but it's so simple that it's + * unlikely to change. + * NOTE: in Postgres 6.4 and later, the be_pid is the notifying backend's, + * whereas in earlier versions it was always your own backend's PID. + */ +typedef struct pgNotify +{ + char *relname; /* notification condition name */ + int be_pid; /* process ID of notifying server process */ + char *extra; /* notification parameter */ + /* Fields below here are private to libpq; apps should not use 'em */ + struct pgNotify *next; /* list link */ +} PGnotify; + +/* Function types for notice-handling callbacks */ +typedef void (*PQnoticeReceiver) (void *arg, const PGresult *res); +typedef void (*PQnoticeProcessor) (void *arg, const char *message); + +/* Print options for PQprint() */ +typedef char pqbool; + +typedef struct _PQprintOpt +{ + pqbool header; /* print output field headings and row count */ + pqbool align; /* fill align the fields */ + pqbool standard; /* old brain dead format */ + pqbool html3; /* output html tables */ + pqbool expanded; /* expand tables */ + pqbool pager; /* use pager for output if needed */ + char *fieldSep; /* field separator */ + char *tableOpt; /* insert to HTML */ + char *caption; /* HTML
*/ + char **fieldName; /* null terminated array of replacement field + * names */ +} PQprintOpt; + +/* ---------------- + * Structure for the conninfo parameter definitions returned by PQconndefaults + * or PQconninfoParse. + * + * All fields except "val" point at static strings which must not be altered. + * "val" is either NULL or a malloc'd current-value string. PQconninfoFree() + * will release both the val strings and the PQconninfoOption array itself. + * ---------------- + */ +typedef struct _PQconninfoOption +{ + char *keyword; /* The keyword of the option */ + char *envvar; /* Fallback environment variable name */ + char *compiled; /* Fallback compiled in default value */ + char *val; /* Option's current value, or NULL */ + char *label; /* Label for field in connect dialog */ + char *dispchar; /* Indicates how to display this field in a + * connect dialog. Values are: "" Display + * entered value as is "*" Password field - + * hide value "D" Debug option - don't show + * by default */ + int dispsize; /* Field size in characters for dialog */ +} PQconninfoOption; + +/* ---------------- + * PQArgBlock -- structure for PQfn() arguments + * ---------------- + */ +typedef struct +{ + int len; + int isint; + union + { + int *ptr; /* can't use void (dec compiler barfs) */ + int integer; + } u; +} PQArgBlock; + +/* ---------------- + * PGresAttDesc -- Data about a single attribute (column) of a query result + * ---------------- + */ +typedef struct pgresAttDesc +{ + char *name; /* column name */ + Oid tableid; /* source table, if known */ + int columnid; /* source column, if known */ + int format; /* format code for value (text/binary) */ + Oid typid; /* type id */ + int typlen; /* type size */ + int atttypmod; /* type-specific modifier info */ +} PGresAttDesc; + +/* ---------------- + * Exported functions of libpq + * ---------------- + */ + +/* === in fe-connect.c === */ + +/* make a new client connection to the backend */ +/* Asynchronous (non-blocking) */ +extern PGconn *PQconnectStart(const char *conninfo); +extern PGconn *PQconnectStartParams(const char *const * keywords, + const char *const * values, int expand_dbname); +extern PostgresPollingStatusType PQconnectPoll(PGconn *conn); + +/* Synchronous (blocking) */ +extern PGconn *PQconnectdb(const char *conninfo); +extern PGconn *PQconnectdbParams(const char *const * keywords, + const char *const * values, int expand_dbname); +extern PGconn *PQsetdbLogin(const char *pghost, const char *pgport, + const char *pgoptions, const char *pgtty, + const char *dbName, + const char *login, const char *pwd); + +#define PQsetdb(M_PGHOST,M_PGPORT,M_PGOPT,M_PGTTY,M_DBNAME) \ + PQsetdbLogin(M_PGHOST, M_PGPORT, M_PGOPT, M_PGTTY, M_DBNAME, NULL, NULL) + +/* close the current connection and free the PGconn data structure */ +extern void PQfinish(PGconn *conn); + +/* get info about connection options known to PQconnectdb */ +extern PQconninfoOption *PQconndefaults(void); + +/* parse connection options in same way as PQconnectdb */ +extern PQconninfoOption *PQconninfoParse(const char *conninfo, char **errmsg); + +/* return the connection options used by a live connection */ +extern PQconninfoOption *PQconninfo(PGconn *conn); + +/* free the data structure returned by PQconndefaults() or PQconninfoParse() */ +extern void PQconninfoFree(PQconninfoOption *connOptions); + +/* + * close the current connection and restablish a new one with the same + * parameters + */ +/* Asynchronous (non-blocking) */ +extern int PQresetStart(PGconn *conn); +extern PostgresPollingStatusType PQresetPoll(PGconn *conn); + +/* Synchronous (blocking) */ +extern void PQreset(PGconn *conn); + +/* request a cancel structure */ +extern PGcancel *PQgetCancel(PGconn *conn); + +/* free a cancel structure */ +extern void PQfreeCancel(PGcancel *cancel); + +/* issue a cancel request */ +extern int PQcancel(PGcancel *cancel, char *errbuf, int errbufsize); + +/* backwards compatible version of PQcancel; not thread-safe */ +extern int PQrequestCancel(PGconn *conn); + +/* Accessor functions for PGconn objects */ +extern char *PQdb(const PGconn *conn); +extern char *PQuser(const PGconn *conn); +extern char *PQpass(const PGconn *conn); +extern char *PQhost(const PGconn *conn); +extern char *PQport(const PGconn *conn); +extern char *PQtty(const PGconn *conn); +extern char *PQoptions(const PGconn *conn); +extern ConnStatusType PQstatus(const PGconn *conn); +extern PGTransactionStatusType PQtransactionStatus(const PGconn *conn); +extern const char *PQparameterStatus(const PGconn *conn, + const char *paramName); +extern int PQprotocolVersion(const PGconn *conn); +extern int PQserverVersion(const PGconn *conn); +extern char *PQerrorMessage(const PGconn *conn); +extern int PQsocket(const PGconn *conn); +extern int PQbackendPID(const PGconn *conn); +extern int PQconnectionNeedsPassword(const PGconn *conn); +extern int PQconnectionUsedPassword(const PGconn *conn); +extern int PQclientEncoding(const PGconn *conn); +extern int PQsetClientEncoding(PGconn *conn, const char *encoding); + +/* SSL information functions */ +extern int PQsslInUse(PGconn *conn); +extern void *PQsslStruct(PGconn *conn, const char *struct_name); +extern const char *PQsslAttribute(PGconn *conn, const char *attribute_name); +extern const char *const * PQsslAttributeNames(PGconn *conn); + +/* Get the OpenSSL structure associated with a connection. Returns NULL for + * unencrypted connections or if any other TLS library is in use. */ +extern void *PQgetssl(PGconn *conn); + +/* Tell libpq whether it needs to initialize OpenSSL */ +extern void PQinitSSL(int do_init); + +/* More detailed way to tell libpq whether it needs to initialize OpenSSL */ +extern void PQinitOpenSSL(int do_ssl, int do_crypto); + +/* Set verbosity for PQerrorMessage and PQresultErrorMessage */ +extern PGVerbosity PQsetErrorVerbosity(PGconn *conn, PGVerbosity verbosity); + +/* Set CONTEXT visibility for PQerrorMessage and PQresultErrorMessage */ +extern PGContextVisibility PQsetErrorContextVisibility(PGconn *conn, + PGContextVisibility show_context); + +/* Enable/disable tracing */ +extern void PQtrace(PGconn *conn, FILE *debug_port); +extern void PQuntrace(PGconn *conn); + +/* Override default notice handling routines */ +extern PQnoticeReceiver PQsetNoticeReceiver(PGconn *conn, + PQnoticeReceiver proc, + void *arg); +extern PQnoticeProcessor PQsetNoticeProcessor(PGconn *conn, + PQnoticeProcessor proc, + void *arg); + +/* + * Used to set callback that prevents concurrent access to + * non-thread safe functions that libpq needs. + * The default implementation uses a libpq internal mutex. + * Only required for multithreaded apps that use kerberos + * both within their app and for postgresql connections. + */ +typedef void (*pgthreadlock_t) (int acquire); + +extern pgthreadlock_t PQregisterThreadLock(pgthreadlock_t newhandler); + +/* === in fe-exec.c === */ + +/* Simple synchronous query */ +extern PGresult *PQexec(PGconn *conn, const char *query); +extern PGresult *PQexecParams(PGconn *conn, + const char *command, + int nParams, + const Oid *paramTypes, + const char *const * paramValues, + const int *paramLengths, + const int *paramFormats, + int resultFormat); +extern PGresult *PQprepare(PGconn *conn, const char *stmtName, + const char *query, int nParams, + const Oid *paramTypes); +extern PGresult *PQexecPrepared(PGconn *conn, + const char *stmtName, + int nParams, + const char *const * paramValues, + const int *paramLengths, + const int *paramFormats, + int resultFormat); + +/* Interface for multiple-result or asynchronous queries */ +extern int PQsendQuery(PGconn *conn, const char *query); +extern int PQsendQueryParams(PGconn *conn, + const char *command, + int nParams, + const Oid *paramTypes, + const char *const * paramValues, + const int *paramLengths, + const int *paramFormats, + int resultFormat); +extern int PQsendPrepare(PGconn *conn, const char *stmtName, + const char *query, int nParams, + const Oid *paramTypes); +extern int PQsendQueryPrepared(PGconn *conn, + const char *stmtName, + int nParams, + const char *const * paramValues, + const int *paramLengths, + const int *paramFormats, + int resultFormat); +extern int PQsetSingleRowMode(PGconn *conn); +extern PGresult *PQgetResult(PGconn *conn); + +/* Routines for managing an asynchronous query */ +extern int PQisBusy(PGconn *conn); +extern int PQconsumeInput(PGconn *conn); + +/* LISTEN/NOTIFY support */ +extern PGnotify *PQnotifies(PGconn *conn); + +/* Routines for copy in/out */ +extern int PQputCopyData(PGconn *conn, const char *buffer, int nbytes); +extern int PQputCopyEnd(PGconn *conn, const char *errormsg); +extern int PQgetCopyData(PGconn *conn, char **buffer, int async); + +/* Deprecated routines for copy in/out */ +extern int PQgetline(PGconn *conn, char *string, int length); +extern int PQputline(PGconn *conn, const char *string); +extern int PQgetlineAsync(PGconn *conn, char *buffer, int bufsize); +extern int PQputnbytes(PGconn *conn, const char *buffer, int nbytes); +extern int PQendcopy(PGconn *conn); + +/* Set blocking/nonblocking connection to the backend */ +extern int PQsetnonblocking(PGconn *conn, int arg); +extern int PQisnonblocking(const PGconn *conn); +extern int PQisthreadsafe(void); +extern PGPing PQping(const char *conninfo); +extern PGPing PQpingParams(const char *const * keywords, + const char *const * values, int expand_dbname); + +/* Force the write buffer to be written (or at least try) */ +extern int PQflush(PGconn *conn); + +/* + * "Fast path" interface --- not really recommended for application + * use + */ +extern PGresult *PQfn(PGconn *conn, + int fnid, + int *result_buf, + int *result_len, + int result_is_int, + const PQArgBlock *args, + int nargs); + +/* Accessor functions for PGresult objects */ +extern ExecStatusType PQresultStatus(const PGresult *res); +extern char *PQresStatus(ExecStatusType status); +extern char *PQresultErrorMessage(const PGresult *res); +extern char *PQresultVerboseErrorMessage(const PGresult *res, + PGVerbosity verbosity, + PGContextVisibility show_context); +extern char *PQresultErrorField(const PGresult *res, int fieldcode); +extern int PQntuples(const PGresult *res); +extern int PQnfields(const PGresult *res); +extern int PQbinaryTuples(const PGresult *res); +extern char *PQfname(const PGresult *res, int field_num); +extern int PQfnumber(const PGresult *res, const char *field_name); +extern Oid PQftable(const PGresult *res, int field_num); +extern int PQftablecol(const PGresult *res, int field_num); +extern int PQfformat(const PGresult *res, int field_num); +extern Oid PQftype(const PGresult *res, int field_num); +extern int PQfsize(const PGresult *res, int field_num); +extern int PQfmod(const PGresult *res, int field_num); +extern char *PQcmdStatus(PGresult *res); +extern char *PQoidStatus(const PGresult *res); /* old and ugly */ +extern Oid PQoidValue(const PGresult *res); /* new and improved */ +extern char *PQcmdTuples(PGresult *res); +extern char *PQgetvalue(const PGresult *res, int tup_num, int field_num); +extern int PQgetlength(const PGresult *res, int tup_num, int field_num); +extern int PQgetisnull(const PGresult *res, int tup_num, int field_num); +extern int PQnparams(const PGresult *res); +extern Oid PQparamtype(const PGresult *res, int param_num); + +/* Describe prepared statements and portals */ +extern PGresult *PQdescribePrepared(PGconn *conn, const char *stmt); +extern PGresult *PQdescribePortal(PGconn *conn, const char *portal); +extern int PQsendDescribePrepared(PGconn *conn, const char *stmt); +extern int PQsendDescribePortal(PGconn *conn, const char *portal); + +/* Delete a PGresult */ +extern void PQclear(PGresult *res); + +/* For freeing other alloc'd results, such as PGnotify structs */ +extern void PQfreemem(void *ptr); + +/* Exists for backward compatibility. bjm 2003-03-24 */ +#define PQfreeNotify(ptr) PQfreemem(ptr) + +/* Error when no password was given. */ +/* Note: depending on this is deprecated; use PQconnectionNeedsPassword(). */ +#define PQnoPasswordSupplied "fe_sendauth: no password supplied\n" + +/* Create and manipulate PGresults */ +extern PGresult *PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status); +extern PGresult *PQcopyResult(const PGresult *src, int flags); +extern int PQsetResultAttrs(PGresult *res, int numAttributes, PGresAttDesc *attDescs); +extern void *PQresultAlloc(PGresult *res, size_t nBytes); +extern int PQsetvalue(PGresult *res, int tup_num, int field_num, char *value, int len); + +/* Quoting strings before inclusion in queries. */ +extern size_t PQescapeStringConn(PGconn *conn, + char *to, const char *from, size_t length, + int *error); +extern char *PQescapeLiteral(PGconn *conn, const char *str, size_t len); +extern char *PQescapeIdentifier(PGconn *conn, const char *str, size_t len); +extern unsigned char *PQescapeByteaConn(PGconn *conn, + const unsigned char *from, size_t from_length, + size_t *to_length); +extern unsigned char *PQunescapeBytea(const unsigned char *strtext, + size_t *retbuflen); + +/* These forms are deprecated! */ +extern size_t PQescapeString(char *to, const char *from, size_t length); +extern unsigned char *PQescapeBytea(const unsigned char *from, size_t from_length, + size_t *to_length); + + + +/* === in fe-print.c === */ + +extern void PQprint(FILE *fout, /* output stream */ + const PGresult *res, + const PQprintOpt *ps); /* option structure */ + +/* + * really old printing routines + */ +extern void PQdisplayTuples(const PGresult *res, + FILE *fp, /* where to send the output */ + int fillAlign, /* pad the fields with spaces */ + const char *fieldSep, /* field separator */ + int printHeader, /* display headers? */ + int quiet); + +extern void PQprintTuples(const PGresult *res, + FILE *fout, /* output stream */ + int printAttName, /* print attribute names */ + int terseOutput, /* delimiter bars */ + int width); /* width of column, if 0, use variable width */ + + +/* === in fe-lobj.c === */ + +/* Large-object access routines */ +extern int lo_open(PGconn *conn, Oid lobjId, int mode); +extern int lo_close(PGconn *conn, int fd); +extern int lo_read(PGconn *conn, int fd, char *buf, size_t len); +extern int lo_write(PGconn *conn, int fd, const char *buf, size_t len); +extern int lo_lseek(PGconn *conn, int fd, int offset, int whence); +extern pg_int64 lo_lseek64(PGconn *conn, int fd, pg_int64 offset, int whence); +extern Oid lo_creat(PGconn *conn, int mode); +extern Oid lo_create(PGconn *conn, Oid lobjId); +extern int lo_tell(PGconn *conn, int fd); +extern pg_int64 lo_tell64(PGconn *conn, int fd); +extern int lo_truncate(PGconn *conn, int fd, size_t len); +extern int lo_truncate64(PGconn *conn, int fd, pg_int64 len); +extern int lo_unlink(PGconn *conn, Oid lobjId); +extern Oid lo_import(PGconn *conn, const char *filename); +extern Oid lo_import_with_oid(PGconn *conn, const char *filename, Oid lobjId); +extern int lo_export(PGconn *conn, Oid lobjId, const char *filename); + +/* === in fe-misc.c === */ + +/* Get the version of the libpq library in use */ +extern int PQlibVersion(void); + +/* Determine length of multibyte encoded char at *s */ +extern int PQmblen(const char *s, int encoding); + +/* Determine display length of multibyte encoded char at *s */ +extern int PQdsplen(const char *s, int encoding); + +/* Get encoding id from environment variable PGCLIENTENCODING */ +extern int PQenv2encoding(void); + +/* === in fe-auth.c === */ + +extern char *PQencryptPassword(const char *passwd, const char *user); + +/* === in encnames.c === */ + +extern int pg_char_to_encoding(const char *name); +extern const char *pg_encoding_to_char(int encoding); +extern int pg_valid_server_encoding_id(int encoding); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBPQ_FE_H */ diff --git a/extensions/pgsql/pgsql/PgBasicResults.cpp b/extensions/pgsql/pgsql/PgBasicResults.cpp new file mode 100644 index 00000000..91276cbc --- /dev/null +++ b/extensions/pgsql/pgsql/PgBasicResults.cpp @@ -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 . + * + * 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 +#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; +} diff --git a/extensions/pgsql/pgsql/PgBasicResults.h b/extensions/pgsql/pgsql/PgBasicResults.h new file mode 100644 index 00000000..d93979ac --- /dev/null +++ b/extensions/pgsql/pgsql/PgBasicResults.h @@ -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 . + * + * 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$ + */ + +#ifndef _INCLUDE_SM_PGSQL_BASIC_RESULTS_H_ +#define _INCLUDE_SM_PGSQL_BASIC_RESULTS_H_ + +#include +#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 m_pParent; + PgBasicResults m_rs; + unsigned int m_InsertID; + unsigned int m_AffectedRows; +}; + +#endif //_INCLUDE_SM_PGSQL_BASIC_RESULTS_H_ diff --git a/extensions/pgsql/pgsql/PgDatabase.cpp b/extensions/pgsql/pgsql/PgDatabase.cpp new file mode 100644 index 00000000..3a9b7fa6 --- /dev/null +++ b/extensions/pgsql/pgsql/PgDatabase.cpp @@ -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 . + * + * 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; +} diff --git a/extensions/pgsql/pgsql/PgDatabase.h b/extensions/pgsql/pgsql/PgDatabase.h new file mode 100644 index 00000000..b55c415f --- /dev/null +++ b/extensions/pgsql/pgsql/PgDatabase.h @@ -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 . + * + * 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$ + */ + +#ifndef _INCLUDE_SM_PGSQL_DATABASE_H_ +#define _INCLUDE_SM_PGSQL_DATABASE_H_ + +#include +#include +#include "PgDriver.h" + +class PgQuery; +class PgStatement; + +class PgDatabase + : public IDatabase, + public ke::RefcountedThreadsafe +{ + 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_ diff --git a/extensions/pgsql/pgsql/PgDriver.cpp b/extensions/pgsql/pgsql/PgDriver.cpp new file mode 100644 index 00000000..cda7aceb --- /dev/null +++ b/extensions/pgsql/pgsql/PgDriver.cpp @@ -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 . + * + * 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 "PgDriver.h" +#include "PgDatabase.h" +#include "smsdk_ext.h" + +PgDriver g_PgDriver; + +PgDriver::PgDriver() +{ + m_Handle = BAD_HANDLE; +} + +void CloseDBList(List &l) +{ + List::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::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 lock(m_Lock); + + if (persistent) + { + /* Try to find a matching persistent connection */ + List::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 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); +} diff --git a/extensions/pgsql/pgsql/PgDriver.h b/extensions/pgsql/pgsql/PgDriver.h new file mode 100644 index 00000000..5a59fe01 --- /dev/null +++ b/extensions/pgsql/pgsql/PgDriver.h @@ -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 . + * + * 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$ + */ + +#ifndef _INCLUDE_SM_PGSQL_DRIVER_H_ +#define _INCLUDE_SM_PGSQL_DRIVER_H_ + +#define SOURCEMOD_SQL_DRIVER_CODE +#include +#include +#if defined PLATFORM_WINDOWS +#include +#endif + +#include + +#include +#include + +#include + +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 m_PermDbs; +}; + +extern PgDriver g_PgDriver; + +unsigned int strncopy(char *dest, const char *src, size_t count); + +#endif //_INCLUDE_SM_PGSQL_DRIVER_H_ diff --git a/extensions/pgsql/pgsql/PgStatement.cpp b/extensions/pgsql/pgsql/PgStatement.cpp new file mode 100644 index 00000000..0c920207 --- /dev/null +++ b/extensions/pgsql/pgsql/PgStatement.cpp @@ -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 . + * + * 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 "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) + { + 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; iSetLastIDAndRows(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); +} diff --git a/extensions/pgsql/pgsql/PgStatement.h b/extensions/pgsql/pgsql/PgStatement.h new file mode 100644 index 00000000..f2218976 --- /dev/null +++ b/extensions/pgsql/pgsql/PgStatement.h @@ -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 . + * + * 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$ + */ + +#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 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_ diff --git a/extensions/pgsql/postgres_ext.h b/extensions/pgsql/postgres_ext.h new file mode 100644 index 00000000..ae2f0877 --- /dev/null +++ b/extensions/pgsql/postgres_ext.h @@ -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 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 */ diff --git a/extensions/pgsql/smsdk_config.h b/extensions/pgsql/smsdk_config.h new file mode 100644 index 00000000..2649d04a --- /dev/null +++ b/extensions/pgsql/smsdk_config.h @@ -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 . + * + * 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$ + */ + +#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_ diff --git a/extensions/pgsql/version.rc b/extensions/pgsql/version.rc new file mode 100644 index 00000000..02533326 --- /dev/null +++ b/extensions/pgsql/version.rc @@ -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 + +///////////////////////////////////////////////////////////////////////////// +#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 + diff --git a/plugins/sql-admin-manager.sp b/plugins/sql-admin-manager.sp index 0ad732b9..f606a293 100644 --- a/plugins/sql-admin-manager.sp +++ b/plugins/sql-admin-manager.sp @@ -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; @@ -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); } @@ -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; @@ -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."); }