Add PostgreSQL database driver (#32)

* Add PostgreSQL DBI extension

Bug #3849
This adds a postgresql database driver called "pgsql".
The ambuild script changes could be very wrong ;)

* Add pgsql support to clientprefs

This was originally made by Lyfe in bug 3849! Thanks for that.
PostgreSQL supports the 'IF NOT EXISTS' clause when creating tables
since version 9.1, so i've switched to use that.

* Added pgsql support to sql-admin-manager

* Add --pgsql-path to ambuild configure

* More ambuild fixes

* Compile libpq with ambuild

* Try to generate postgres configs

* Add dummy config file

* More ambuild fixes

* Get errorcode and readable error in preparequery

No need to stop getting the errorCode, if the error string should be
retrieved. Just return both!

* Fix indentation in AMBuilderPGSQL

* Try to patch and configure postgres through ambuild

* Revert "Try to patch and configure postgres through ambuild"

This reverts commit 68dfc82b8eb0ce11f621af67ec247b5f47e4a189.

* Update to use PostgreSQL 9.4

* Move postgres preparation into seperate script

Fetching, preparing and configuring of the postgresql sourcecode
is done in a seperate prepare_postgresql.sh script now.

People can use this script for their manual builds,
if they don't want to use checkout-deps.sh.

* Add patch to configure.in again

Remove the version check for autoconf in postgres 9.4 too.

* Nit: Support older objdirs for hasPgSql ambuild option

* Update to use PostgreSQL 9.4.6

* Use newer AMTL and PGSQL 9.6

* Fix threaded queries

When running the thread part of a query, it already gets the atomic
lock. Don't try to get it twice in that case.

Use a seperate lock to protect access to the lastInsertID and
lastAffectedRows variables.

* Fix SetCharacterSet racing with threaded queries

Same fix like in the mysql driver.

* Use ke::AutoLock for lastInsertID mutex

* Fix fetching one more row than available

Don't try to access a row that doesn't exist when iterating rows.

* Improve sanity checks on column access

Don't call libpq functions with out of bounds column indices.

* Let QuoteString return false if quoting failed

* Fix build for x64 support changes and update to PGSQL 9.6.9

* Fix linux build

* Fix building of connection options string

snprintf doesn't support overlapping buffers.

* Update to PostgreSQL 9.6.15

* Fix crash after freeing first IQuery object

* Fix code crunch

* Fix memory leak, cleanup

* Nuke MSVC project and Makefile

* Remove unsupported numeric error codes

* Use STL threads

* Add prebuilt libpq for Linux and Windows

* Restore and fix version.rc file

* Add PostgreSQL build instructions

* Add prebuilt libpq for Mac

* Add win64 libpq build

This is version 9.6.15 since that's what I still had laying around.

* Fix buildscript

* Fix x64 build on linux and mac

Co-authored-by: Erik Minekus <tsunami@tsunami-productions.nl>
This commit is contained in:
peace-maker 2021-03-11 11:21:51 +01:00 committed by GitHub
parent 3c30f7b971
commit e5342afe2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 3085 additions and 1 deletions

View File

@ -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',

View File

@ -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;

View File

@ -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');

View File

@ -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);

View File

@ -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

View File

@ -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))
{

View File

@ -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)]

View File

@ -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`

View File

@ -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 <http://www.gnu.org/licenses/>.
*
* As a special exception, AlliedModders LLC gives you permission to link the
* code of this program (as well as its derivative works) to "Half-Life 2," the
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
* by the Valve Corporation. You must obey the GNU General Public License in
* all respects for all other code used. Additionally, AlliedModders LLC grants
* this exception to all derivative works. AlliedModders LLC defines further
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
* or <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/
#include <sourcemod_version.h>
#include "extension.h"
#include "pgsql/PgDriver.h"
#include <assert.h>
#include <stdlib.h>
/**
* @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;
}

View File

@ -0,0 +1,122 @@
/**
* vim: set ts=4 :
* =============================================================================
* SourceMod PostgreSQL Extension
* Copyright (C) 2013 AlliedModders LLC. All rights reserved.
* =============================================================================
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 3.0, as published by the
* Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, AlliedModders LLC gives you permission to link the
* code of this program (as well as its derivative works) to "Half-Life 2," the
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
* by the Valve Corporation. You must obey the GNU General Public License in
* all respects for all other code used. Additionally, AlliedModders LLC grants
* this exception to all derivative works. AlliedModders LLC defines further
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
* or <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/
#ifndef _INCLUDE_SOURCEMOD_EXTENSION_PROPER_H_
#define _INCLUDE_SOURCEMOD_EXTENSION_PROPER_H_
/**
* @file extension.h
* @brief Sample extension code header.
*/
#define SOURCEMOD_SQL_DRIVER_CODE
#include "smsdk_ext.h"
/**
* @brief Sample implementation of the SDK Extension.
* Note: Uncomment one of the pre-defined virtual functions in order to use it.
*/
class DBI_PgSQL : public SDKExtension
{
public:
/**
* @brief This is called after the initial loading sequence has been processed.
*
* @param error Error message buffer.
* @param maxlength Size of error message buffer.
* @param late Whether or not the module was loaded after map load.
* @return True to succeed loading, false to fail.
*/
virtual bool SDK_OnLoad(char *error, size_t maxlength, bool late);
/**
* @brief This is called right before the extension is unloaded.
*/
virtual void SDK_OnUnload();
/**
* @brief This is called once all known extensions have been loaded.
* Note: It is is a good idea to add natives here, if any are provided.
*/
//virtual void SDK_OnAllLoaded();
/**
* @brief Called when the pause state is changed.
*/
//virtual void SDK_OnPauseChange(bool paused);
/**
* @brief this is called when Core wants to know if your extension is working.
*
* @param error Error message buffer.
* @param maxlength Size of error message buffer.
* @return True if working, false otherwise.
*/
//virtual bool QueryRunning(char *error, size_t maxlength);
const char *GetExtensionVerString();
const char *GetExtensionDateString();
public:
#if defined SMEXT_CONF_METAMOD
/**
* @brief Called when Metamod is attached, before the extension version is called.
*
* @param error Error buffer.
* @param maxlength Maximum size of error buffer.
* @param late Whether or not Metamod considers this a late load.
* @return True to succeed, false to fail.
*/
//virtual bool SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxlength, bool late);
/**
* @brief Called when Metamod is detaching, after the extension version is called.
* NOTE: By default this is blocked unless sent from SourceMod.
*
* @param error Error buffer.
* @param maxlength Maximum size of error buffer.
* @return True to succeed, false to fail.
*/
//virtual bool SDK_OnMetamodUnload(char *error, size_t maxlength);
/**
* @brief Called when Metamod's pause state is changing.
* NOTE: By default this is blocked unless sent from SourceMod.
*
* @param paused Pause state being set.
* @param error Error buffer.
* @param maxlength Maximum size of error buffer.
* @return True to succeed, false to fail.
*/
//virtual bool SDK_OnMetamodPauseChange(bool paused, char *error, size_t maxlength);
#endif
};
#endif // _INCLUDE_SOURCEMOD_EXTENSION_PROPER_H_

Binary file not shown.

View File

@ -0,0 +1,8 @@
/* src/include/pg_config_ext.h. Generated from pg_config_ext.h.in by configure. */
/*
* src/include/pg_config_ext.h.in. This is generated manually, not by
* autoheader, since we want to limit which symbols get defined here.
*/
/* Define to the name of a signed 64-bit integer type. */
#define PG_INT64_TYPE long long int

Binary file not shown.

View File

@ -0,0 +1,8 @@
/* src/include/pg_config_ext.h. Generated from pg_config_ext.h.in by configure. */
/*
* src/include/pg_config_ext.h.in. This is generated manually, not by
* autoheader, since we want to limit which symbols get defined here.
*/
/* Define to the name of a signed 64-bit integer type. */
#define PG_INT64_TYPE long int

Binary file not shown.

View File

@ -0,0 +1,8 @@
/* src/include/pg_config_ext.h. Generated from pg_config_ext.h.in by configure. */
/*
* src/include/pg_config_ext.h.in. This is generated manually, not by
* autoheader, since we want to limit which symbols get defined here.
*/
/* Define to the name of a signed 64-bit integer type. */
#define PG_INT64_TYPE long long int

Binary file not shown.

View File

@ -0,0 +1,8 @@
/* src/include/pg_config_ext.h. Generated from pg_config_ext.h.in by configure. */
/*
* src/include/pg_config_ext.h.in. This is generated manually, not by
* autoheader, since we want to limit which symbols get defined here.
*/
/* Define to the name of a signed 64-bit integer type. */
#define PG_INT64_TYPE long int

Binary file not shown.

View File

@ -0,0 +1,7 @@
/*
* src/include/pg_config_ext.h.win32. This is generated manually, not by
* autoheader, since we want to limit which symbols get defined here.
*/
/* Define to the name of a signed 64-bit integer type. */
#define PG_INT64_TYPE long long int

Binary file not shown.

View File

@ -0,0 +1,7 @@
/*
* src/include/pg_config_ext.h.win32. This is generated manually, not by
* autoheader, since we want to limit which symbols get defined here.
*/
/* Define to the name of a signed 64-bit integer type. */
#define PG_INT64_TYPE long long int

607
extensions/pgsql/libpq-fe.h Normal file
View File

@ -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 <stdio.h>
/*
* 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 <table ...> */
char *caption; /* HTML <caption> */
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 */

View File

@ -0,0 +1,350 @@
/**
* vim: set ts=4 :
* =============================================================================
* SourceMod PostgreSQL Extension
* Copyright (C) 2013 AlliedModders LLC. All rights reserved.
* =============================================================================
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 3.0, as published by the
* Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, AlliedModders LLC gives you permission to link the
* code of this program (as well as its derivative works) to "Half-Life 2," the
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
* by the Valve Corporation. You must obey the GNU General Public License in
* all respects for all other code used. Additionally, AlliedModders LLC grants
* this exception to all derivative works. AlliedModders LLC defines further
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
* or <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/
#include <stdlib.h>
#include "PgBasicResults.h"
PgBasicResults::PgBasicResults(PGresult *res)
: m_pRes(res)
{
Update();
}
PgBasicResults::~PgBasicResults()
{
}
void PgBasicResults::Update()
{
if (m_pRes)
{
m_ColCount = (unsigned int)PQnfields(m_pRes);
m_RowCount = (unsigned int)PQntuples(m_pRes);
m_CurRow = 0;
m_RowFetched = false;
}
}
unsigned int PgBasicResults::GetRowCount()
{
return m_RowCount;
}
unsigned int PgBasicResults::GetFieldCount()
{
return m_ColCount;
}
bool PgBasicResults::FieldNameToNum(const char *name, unsigned int *columnId)
{
int id = PQfnumber(m_pRes, name);
if (id == -1)
return false;
*columnId = (unsigned int)id;
return true;
}
const char *PgBasicResults::FieldNumToName(unsigned int colId)
{
if (colId >= m_ColCount)
return nullptr;
return PQfname(m_pRes, colId);
}
bool PgBasicResults::MoreRows()
{
return ((!m_RowFetched && m_RowCount > 0) || (m_RowFetched && m_CurRow < m_RowCount-1));
}
IResultRow *PgBasicResults::FetchRow()
{
// Rows start at 0. Start incrementing the current row when fetching the second time.
if (m_RowFetched)
m_CurRow++;
// Reached the end of the rows.
if (m_CurRow >= m_RowCount)
{
m_CurRow = m_RowCount;
return nullptr;
}
// We fetched some rows.
m_RowFetched = true;
return this;
}
IResultRow *PgBasicResults::CurrentRow()
{
if (!m_pRes
|| !m_RowFetched
|| m_CurRow >= m_RowCount)
{
return nullptr;
}
return this;
}
bool PgBasicResults::Rewind()
{
m_RowFetched = false;
m_CurRow = 0;
return true;
}
DBType PgBasicResults::GetFieldType(unsigned int field)
{
if (field >= m_ColCount)
{
return DBType_Unknown;
}
Oid fld = PQftype(m_pRes, field);
if (fld == InvalidOid)
{
return DBType_Unknown;
}
return GetOurType(fld);
}
DBType PgBasicResults::GetFieldDataType(unsigned int field)
{
if (field >= m_ColCount)
{
return DBType_Unknown;
}
if (PQfformat(m_pRes, field) == 1)
{
return DBType_Blob;
} else {
return DBType_String;
}
}
bool PgBasicResults::IsNull(unsigned int columnId)
{
if (columnId >= m_ColCount)
{
return true;
}
return (PQgetisnull(m_pRes, m_CurRow, columnId) == 1);
}
DBResult PgBasicResults::GetString(unsigned int columnId, const char **pString, size_t *length)
{
if (columnId >= m_ColCount)
{
return DBVal_Error;
} else if (IsNull(columnId)) {
*pString = "";
if (length)
{
*length = 0;
}
return DBVal_Null;
}
*pString = PQgetvalue(m_pRes, m_CurRow, columnId);
if (length)
{
*length = GetDataSize(columnId);
}
return DBVal_Data;
}
DBResult PgBasicResults::CopyString(unsigned int columnId,
char *buffer,
size_t maxlength,
size_t *written)
{
DBResult res;
const char *str;
if ((res=GetString(columnId, &str, nullptr)) == DBVal_Error)
{
return DBVal_Error;
}
size_t wr = strncopy(buffer, str, maxlength);
if (written)
{
*written = wr;
}
return res;
}
size_t PgBasicResults::GetDataSize(unsigned int columnId)
{
if (columnId >= m_ColCount)
{
return 0;
}
return (size_t)PQgetlength(m_pRes, m_CurRow, columnId);
}
DBResult PgBasicResults::GetFloat(unsigned int col, float *fval)
{
if (col >= m_ColCount)
{
return DBVal_Error;
} else if (IsNull(col)) {
*fval = 0.0f;
return DBVal_Null;
}
*fval = (float)atof(PQgetvalue(m_pRes, m_CurRow, col));
return DBVal_Data;
}
DBResult PgBasicResults::GetInt(unsigned int col, int *val)
{
if (col >= m_ColCount)
{
return DBVal_Error;
} else if (IsNull(col)) {
*val = 0;
return DBVal_Null;
}
*val = atoi(PQgetvalue(m_pRes, m_CurRow, col));
return DBVal_Data;
}
DBResult PgBasicResults::GetBlob(unsigned int col, const void **pData, size_t *length)
{
if (col >= m_ColCount)
{
return DBVal_Error;
} else if (IsNull(col)) {
*pData = nullptr;
if (length)
{
*length = 0;
}
return DBVal_Null;
}
*pData = PQgetvalue(m_pRes, m_CurRow, col);
if (length)
{
*length = GetDataSize(col);
}
return DBVal_Data;
}
DBResult PgBasicResults::CopyBlob(unsigned int columnId, void *buffer, size_t maxlength, size_t *written)
{
const void *addr;
size_t length;
DBResult res;
if ((res=GetBlob(columnId, &addr, &length)) == DBVal_Error)
{
return DBVal_Error;
}
if (!addr)
{
return DBVal_Null;
}
if (length > maxlength)
{
length = maxlength;
}
memcpy(buffer, addr, length);
if (written)
{
*written = length;
}
return res;
}
PgQuery::PgQuery(PgDatabase *db, PGresult *res)
: m_pParent(db), m_rs(res)
{
m_InsertID = (unsigned int)PQoidValue(res);
m_AffectedRows = atoi(PQcmdTuples(res));
m_pParent->SetLastIDAndRows(m_InsertID, m_AffectedRows);
}
IResultSet *PgQuery::GetResultSet()
{
if (!m_rs.m_pRes)
{
return nullptr;
}
return &m_rs;
}
unsigned int PgQuery::GetInsertID()
{
return m_InsertID;
}
unsigned int PgQuery::GetAffectedRows()
{
return m_AffectedRows;
}
bool PgQuery::FetchMoreResults()
{
return false;
}
void PgQuery::Destroy()
{
if (m_rs.m_pRes != nullptr)
{
PQclear(m_rs.m_pRes);
}
/* Self destruct */
delete this;
}

View File

@ -0,0 +1,102 @@
/**
* vim: set ts=4 :
* =============================================================================
* SourceMod PostgreSQL Extension
* Copyright (C) 2013 AlliedModders LLC. All rights reserved.
* =============================================================================
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 3.0, as published by the
* Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, AlliedModders LLC gives you permission to link the
* code of this program (as well as its derivative works) to "Half-Life 2," the
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
* by the Valve Corporation. You must obey the GNU General Public License in
* all respects for all other code used. Additionally, AlliedModders LLC grants
* this exception to all derivative works. AlliedModders LLC defines further
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
* or <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/
#ifndef _INCLUDE_SM_PGSQL_BASIC_RESULTS_H_
#define _INCLUDE_SM_PGSQL_BASIC_RESULTS_H_
#include <amtl/am-refcounting.h>
#include "PgDatabase.h"
class PgQuery;
class PgStatement;
class PgBasicResults :
public IResultSet,
public IResultRow
{
friend class PgQuery;
friend class PgStatement;
public:
PgBasicResults(PGresult *res);
~PgBasicResults();
public: //IResultSet
unsigned int GetRowCount();
unsigned int GetFieldCount();
const char *FieldNumToName(unsigned int columnId);
bool FieldNameToNum(const char *name, unsigned int *columnId);
bool MoreRows();
IResultRow *FetchRow();
bool Rewind();
DBType GetFieldType(unsigned int field);
DBType GetFieldDataType(unsigned int field);
IResultRow *CurrentRow();
public: //IResultRow
DBResult GetString(unsigned int columnId, const char **pString, size_t *length);
DBResult GetFloat(unsigned int columnId, float *pFloat);
DBResult GetInt(unsigned int columnId, int *pInt);
bool IsNull(unsigned int columnId);
DBResult GetBlob(unsigned int columnId, const void **pData, size_t *length);
DBResult CopyBlob(unsigned int columnId, void *buffer, size_t maxlength, size_t *written);
DBResult CopyString(unsigned int columnId,
char *buffer,
size_t maxlength,
size_t *written);
size_t GetDataSize(unsigned int columnId);
protected:
void Update();
private:
PGresult *m_pRes;
bool m_RowFetched;
unsigned int m_CurRow;
unsigned int m_ColCount;
unsigned int m_RowCount;
};
class PgQuery : public IQuery
{
friend class PgBasicResults;
public:
PgQuery(PgDatabase *db, PGresult *res);
public:
IResultSet *GetResultSet();
bool FetchMoreResults();
void Destroy();
public: // Used by the driver to implement GetInsertIDForQuery()/GetAffectedRowsForQuery()
unsigned int GetInsertID();
unsigned int GetAffectedRows();
private:
ke::RefPtr<PgDatabase> m_pParent;
PgBasicResults m_rs;
unsigned int m_InsertID;
unsigned int m_AffectedRows;
};
#endif //_INCLUDE_SM_PGSQL_BASIC_RESULTS_H_

View File

@ -0,0 +1,346 @@
/**
* vim: set ts=4 :
* =============================================================================
* SourceMod PostgreSQL Extension
* Copyright (C) 2013 AlliedModders LLC. All rights reserved.
* =============================================================================
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 3.0, as published by the
* Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, AlliedModders LLC gives you permission to link the
* code of this program (as well as its derivative works) to "Half-Life 2," the
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
* by the Valve Corporation. You must obey the GNU General Public License in
* all respects for all other code used. Additionally, AlliedModders LLC grants
* this exception to all derivative works. AlliedModders LLC defines further
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
* or <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/
#include "PgDatabase.h"
#include "smsdk_ext.h"
#include "PgBasicResults.h"
#include "PgStatement.h"
// Some selected defines from postgresql 9.2.4's src/include/catalog/pg_type.h
// Fast scan to extract the types that shouldn't be read as string.
// Floats
#define FLOAT4OID 700
#define FLOAT8OID 701
#define NUMERICOID 1700
// Integer
#define BOOLOID 16
#define INT8OID 20
#define INT2OID 21
#define INT4OID 23
#define OIDOID 26
#define TIDOID 27
#define XIDOID 28
#define CIDOID 29
#define ABSTIMEOID 702
#define RELTIMEOID 703
#define TINTERVALOID 704
#define REFCURSOROID 1790
// String
#define BYTEAOID 17 // Hrmpf. Could be both, string or blob binary data..
#define CHAROID 18
#define NAMEOID 19
#define TEXTOID 25
#define BPCHAROID 1042
#define VARCHAROID 1043
#define DATEOID 1082
#define TIMEOID 1083
// Blob
#define POINTOID 600
#define LSEGOID 601
#define PATHOID 602
#define BOXOID 603
#define POLYGONOID 604
DBType GetOurType(Oid type)
{
switch (type)
{
case FLOAT4OID:
case FLOAT8OID:
{
return DBType_Float;
}
case BOOLOID:
case INT8OID:
case INT2OID:
case INT4OID:
case OIDOID:
case TIDOID:
case XIDOID:
case CIDOID:
case ABSTIMEOID:
case RELTIMEOID:
case TINTERVALOID:
case REFCURSOROID:
{
return DBType_Integer;
}
case BYTEAOID:
case CHAROID:
case NAMEOID:
case TEXTOID:
case BPCHAROID:
case VARCHAROID:
case DATEOID:
case TIMEOID:
{
return DBType_String;
}
case POINTOID:
case LSEGOID:
case PATHOID:
case BOXOID:
case POLYGONOID:
{
return DBType_Blob;
}
default:
{
return DBType_String;
}
}
return DBType_Unknown;
}
PgDatabase::PgDatabase(PGconn *pgsql, const DatabaseInfo *info, bool persistent)
: m_pgsql(pgsql), m_lastInsertID(0), m_lastAffectedRows(0), m_preparedStatementID(0),
m_bPersistent(persistent)
{
m_Host.assign(info->host);
m_Database.assign(info->database);
m_User.assign(info->user);
m_Pass.assign(info->pass);
m_Info.database = m_Database.c_str();
m_Info.host = m_Host.c_str();
m_Info.user = m_User.c_str();
m_Info.pass = m_Pass.c_str();
m_Info.driver = NULL;
m_Info.maxTimeout = info->maxTimeout;
m_Info.port = info->port;
// DBI, for historical reasons, guarantees an initial refcount of 1.
AddRef();
}
PgDatabase::~PgDatabase()
{
if (m_bPersistent)
g_PgDriver.RemoveFromList(this, true);
PQfinish(m_pgsql);
// libpg doesn't keep track of open resultsets of a connection.
// maybe we should track all opened results and clear them here?
// Otherwise there might be a memory leak, if the plugin doesn't close all result handles properly.
// There is no restriction on the order of closing the database connection and the result handles though.
}
void PgDatabase::IncReferenceCount()
{
AddRef();
}
void PgDatabase::SetLastIDAndRows(unsigned int insertID, unsigned int affectedRows)
{
std::lock_guard<std::mutex> lock(m_LastQueryInfoLock);
// Also remember the last query's insert id and affected rows. postgresql only stores them per query.
m_lastInsertID = insertID;
m_lastAffectedRows = affectedRows;
}
bool PgDatabase::Close()
{
return !Release();
}
const DatabaseInfo &PgDatabase::GetInfo()
{
return m_Info;
}
unsigned int PgDatabase::GetInsertID()
{
std::lock_guard<std::mutex> lock(m_LastQueryInfoLock);
return m_lastInsertID;
}
unsigned int PgDatabase::GetAffectedRows()
{
std::lock_guard<std::mutex> lock(m_LastQueryInfoLock);
return m_lastAffectedRows;
}
const char *PgDatabase::GetError(int *errCode)
{
if (errCode)
{
// PostgreSQL only supports SQLSTATE error codes.
// https://www.postgresql.org/docs/9.6/errcodes-appendix.html
*errCode = -1;
}
return PQerrorMessage(m_pgsql);
}
bool PgDatabase::QuoteString(const char *str, char buffer[], size_t maxlength, size_t *newSize)
{
size_t size = strlen(str);
size_t needed = size * 2 + 1;
if (maxlength < needed)
{
if (newSize)
{
*newSize = (size_t)needed;
}
return false;
}
int error = 0;
needed = PQescapeStringConn(m_pgsql, buffer, str, size, &error);
if (newSize)
{
*newSize = (size_t)needed;
}
return error == 0;
}
bool PgDatabase::DoSimpleQuery(const char *query)
{
IQuery *pQuery = DoQuery(query);
if (!pQuery)
{
return false;
}
pQuery->Destroy();
return true;
}
IQuery *PgDatabase::DoQuery(const char *query)
{
PGresult *res = PQexec(m_pgsql, query);
ExecStatusType status = PQresultStatus(res);
if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK)
{
PQclear(res);
return NULL;
}
return new PgQuery(this, res);
}
bool PgDatabase::DoSimpleQueryEx(const char *query, size_t len)
{
IQuery *pQuery = DoQueryEx(query, len);
if (!pQuery)
{
return false;
}
pQuery->Destroy();
return true;
}
IQuery *PgDatabase::DoQueryEx(const char *query, size_t len)
{
// There is no way to send binary data like that in queries.
// You'd need to escape the value with PQescapeByteaConn first and use it in the query string as usual.
return DoQuery(query);
}
unsigned int PgDatabase::GetAffectedRowsForQuery(IQuery *query)
{
return static_cast<PgQuery*>(query)->GetAffectedRows();
}
unsigned int PgDatabase::GetInsertIDForQuery(IQuery *query)
{
return static_cast<PgQuery*>(query)->GetInsertID();
}
IPreparedQuery *PgDatabase::PrepareQuery(const char *query, char *error, size_t maxlength, int *errCode)
{
char stmtName[10];
// Maybe needs some locking around m_preparedStatementID++?
unsigned int stmtID = m_preparedStatementID;
snprintf(stmtName, 10, "%d", stmtID);
m_preparedStatementID++;
// Let postgresql guess the types of the arguments if there are any..
PGresult *res = PQprepare(m_pgsql, stmtName, query, 0, NULL);
if (PQresultStatus(res) != PGRES_COMMAND_OK)
{
if (error)
{
strncopy(error, PQresultErrorMessage(res), maxlength);
}
if (errCode)
{
// PostgreSQL only supports SQLSTATE error codes.
// https://www.postgresql.org/docs/9.6/errcodes-appendix.html
*errCode = -1;
}
PQclear(res);
return NULL;
}
// Only the statement name is of importance. free the PGresult here.
PQclear(res);
return new PgStatement(this, stmtName);
}
bool PgDatabase::LockForFullAtomicOperation()
{
m_FullLock.lock();
return true;
}
void PgDatabase::UnlockFromFullAtomicOperation()
{
m_FullLock.unlock();
}
IDBDriver *PgDatabase::GetDriver()
{
return &g_PgDriver;
}
bool PgDatabase::SetCharacterSet(const char *characterset)
{
bool res;
LockForFullAtomicOperation();
res = PQsetClientEncoding(m_pgsql, characterset) == 0;
UnlockFromFullAtomicOperation();
return res;
}

View File

@ -0,0 +1,93 @@
/**
* vim: set ts=4 :
* =============================================================================
* SourceMod PostgreSQL Extension
* Copyright (C) 2013 AlliedModders LLC. All rights reserved.
* =============================================================================
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 3.0, as published by the
* Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, AlliedModders LLC gives you permission to link the
* code of this program (as well as its derivative works) to "Half-Life 2," the
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
* by the Valve Corporation. You must obey the GNU General Public License in
* all respects for all other code used. Additionally, AlliedModders LLC grants
* this exception to all derivative works. AlliedModders LLC defines further
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
* or <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/
#ifndef _INCLUDE_SM_PGSQL_DATABASE_H_
#define _INCLUDE_SM_PGSQL_DATABASE_H_
#include <amtl/am-refcounting-threadsafe.h>
#include <mutex>
#include "PgDriver.h"
class PgQuery;
class PgStatement;
class PgDatabase
: public IDatabase,
public ke::RefcountedThreadsafe<PgDatabase>
{
friend class PgQuery;
friend class PgStatement;
public:
PgDatabase(PGconn *pgsql, const DatabaseInfo *info, bool persistent);
~PgDatabase();
public: //IDatabase
bool Close();
const char *GetError(int *errorCode=NULL);
bool DoSimpleQuery(const char *query);
IQuery *DoQuery(const char *query);
IPreparedQuery *PrepareQuery(const char *query, char *error, size_t maxlength, int *errCode=NULL);
bool QuoteString(const char *str, char buffer[], size_t maxlen, size_t *newSize);
unsigned int GetAffectedRows();
unsigned int GetInsertID();
bool LockForFullAtomicOperation();
void UnlockFromFullAtomicOperation();
void IncReferenceCount();
IDBDriver *GetDriver();
bool DoSimpleQueryEx(const char *query, size_t len);
IQuery *DoQueryEx(const char *query, size_t len);
unsigned int GetAffectedRowsForQuery(IQuery *query);
unsigned int GetInsertIDForQuery(IQuery *query);
bool SetCharacterSet(const char *characterset);
public:
const DatabaseInfo &GetInfo();
void SetLastIDAndRows(unsigned int insertID, unsigned int affectedRows);
private:
PGconn *m_pgsql;
std::mutex m_FullLock;
unsigned int m_lastInsertID;
unsigned int m_lastAffectedRows;
std::mutex m_LastQueryInfoLock;
unsigned int m_preparedStatementID;
/* ---------- */
DatabaseInfo m_Info;
String m_Host;
String m_Database;
String m_User;
String m_Pass;
bool m_bPersistent;
};
DBType GetOurType(Oid type);
#endif //_INCLUDE_SM_PGSQL_DATABASE_H_

View File

@ -0,0 +1,238 @@
/**
* vim: set ts=4 :
* =============================================================================
* SourceMod PostgreSQL Extension
* Copyright (C) 2013 AlliedModders LLC. All rights reserved.
* =============================================================================
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 3.0, as published by the
* Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, AlliedModders LLC gives you permission to link the
* code of this program (as well as its derivative works) to "Half-Life 2," the
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
* by the Valve Corporation. You must obey the GNU General Public License in
* all respects for all other code used. Additionally, AlliedModders LLC grants
* this exception to all derivative works. AlliedModders LLC defines further
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
* or <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/
#include "PgDriver.h"
#include "PgDatabase.h"
#include "smsdk_ext.h"
PgDriver g_PgDriver;
PgDriver::PgDriver()
{
m_Handle = BAD_HANDLE;
}
void CloseDBList(List<PgDatabase *> &l)
{
List<PgDatabase *>::iterator iter;
for (iter=l.begin(); iter!=l.end(); iter++)
{
PgDatabase *db = (*iter);
while (!db->Close())
{
/* Spool until it closes */
}
}
l.clear();
}
void PgDriver::Shutdown()
{
List<PgDatabase *>::iterator iter;
CloseDBList(m_PermDbs);
if (m_Handle != BAD_HANDLE)
{
dbi->ReleaseHandle(m_Handle, DBHandle_Driver, myself->GetIdentity());
m_Handle = BAD_HANDLE;
}
}
const char *PgDriver::GetIdentifier()
{
return "pgsql";
}
Handle_t PgDriver::GetHandle()
{
if (m_Handle == BAD_HANDLE)
{
m_Handle = dbi->CreateHandle(DBHandle_Driver, this, myself->GetIdentity());
}
return m_Handle;
}
IdentityToken_t *PgDriver::GetIdentity()
{
return myself->GetIdentity();
}
const char *PgDriver::GetProductName()
{
return "PostgreSQL";
}
PGconn *Connect(const DatabaseInfo *info, char *error, size_t maxlength)
{
/* https://www.postgresql.org/docs/9.6/libpq-connect.html#LIBPQ-CONNSTRING */
/* TODO: Switch to PQconnectdbParams to prevent escaping issues. */
char *options = new char[1024];
int offs = snprintf(options, 1024, "host='%s' dbname='%s'", info->host, info->database);
if (info->port > 0)
{
offs += snprintf(&options[offs], 1024 - offs, " port=%d", info->port);
}
if (info->user[0] != '\0')
{
offs += snprintf(&options[offs], 1024 - offs, " user='%s'", info->user);
}
if (info->pass[0] != '\0')
{
offs += snprintf(&options[offs], 1024 - offs, " password='%s'", info->pass);
}
if (info->maxTimeout > 0)
{
offs += snprintf(&options[offs], 1024 - offs, " connect_timeout=%d", info->maxTimeout);
}
/* Make a connection to the database */
PGconn *conn = PQconnectdb(options);
delete [] options;
/* Check to see that the backend connection was successfully made */
if (PQstatus(conn) != CONNECTION_OK)
{
/* :TODO: expose UTIL_Format from smutil! */
snprintf(error, maxlength, "%s", PQerrorMessage(conn));
PQfinish(conn);
return NULL;
}
// :TODO: Check for connection problems everytime we talk to the database server and call PQreset on it, when it fails.
// There is no automatic reconnect in libpg.
// While we're at that. Save the prepared queries and rerun them when we reconnect!
return conn;
}
bool CompareField(const char *str1, const char *str2)
{
if ((str1 == NULL && str2 != NULL)
|| (str1 != NULL && str2 == NULL))
{
return false;
}
if (str1 == NULL && str2 == NULL)
{
return true;
}
return (strcmp(str1, str2) == 0);
}
IDatabase *PgDriver::Connect(const DatabaseInfo *info, bool persistent, char *error, size_t maxlength)
{
std::lock_guard<std::mutex> lock(m_Lock);
if (persistent)
{
/* Try to find a matching persistent connection */
List<PgDatabase *>::iterator iter;
for (iter=m_PermDbs.begin();
iter!=m_PermDbs.end();
iter++)
{
PgDatabase *db = (*iter);
const DatabaseInfo &other = db->GetInfo();
if (CompareField(info->host, other.host)
&& CompareField(info->user, other.user)
&& CompareField(info->pass, other.pass)
&& CompareField(info->database, other.database)
&& (info->port == other.port))
{
db->IncReferenceCount();
return db;
}
}
}
PGconn *pgsql = ::Connect(info, error, maxlength);
if (!pgsql)
{
return NULL;
}
PgDatabase *db = new PgDatabase(pgsql, info, persistent);
if (persistent)
{
m_PermDbs.push_back(db);
}
return db;
}
void PgDriver::RemoveFromList(PgDatabase *pdb, bool persistent)
{
std::lock_guard<std::mutex> lock(m_Lock);
if (persistent)
{
m_PermDbs.remove(pdb);
}
}
bool PgDriver::IsThreadSafe()
{
return (PQisthreadsafe() != 0);
}
bool PgDriver::InitializeThreadSafety()
{
return IsThreadSafe();
}
void PgDriver::ShutdownThreadSafety()
{
return;
}
unsigned int strncopy(char *dest, const char *src, size_t count)
{
if (!count)
{
return 0;
}
char *start = dest;
while ((*src) && (--count))
{
*dest++ = *src++;
}
*dest = '\0';
return (dest - start);
}

View File

@ -0,0 +1,82 @@
/**
* vim: set ts=4 :
* =============================================================================
* SourceMod PostgreSQL Extension
* Copyright (C) 2013 AlliedModders LLC. All rights reserved.
* =============================================================================
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 3.0, as published by the
* Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, AlliedModders LLC gives you permission to link the
* code of this program (as well as its derivative works) to "Half-Life 2," the
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
* by the Valve Corporation. You must obey the GNU General Public License in
* all respects for all other code used. Additionally, AlliedModders LLC grants
* this exception to all derivative works. AlliedModders LLC defines further
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
* or <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/
#ifndef _INCLUDE_SM_PGSQL_DRIVER_H_
#define _INCLUDE_SM_PGSQL_DRIVER_H_
#define SOURCEMOD_SQL_DRIVER_CODE
#include <IDBDriver.h>
#include <sm_platform.h>
#if defined PLATFORM_WINDOWS
#include <winsock.h>
#endif
#include <libpq-fe.h>
#include <sh_string.h>
#include <sh_list.h>
#include <mutex>
using namespace SourceMod;
using namespace SourceHook;
#define M_CLIENT_MULTI_RESULTS ((1) << 17) /* Enable/disable multi-results */
class PgDatabase;
class PgDriver : public IDBDriver
{
public:
PgDriver();
public: //IDBDriver
IDatabase *Connect(const DatabaseInfo *info, bool persistent, char *error, size_t maxlength);
const char *GetIdentifier();
const char *GetProductName();
Handle_t GetHandle();
IdentityToken_t *GetIdentity();
bool IsThreadSafe();
bool InitializeThreadSafety();
void ShutdownThreadSafety();
public:
void Shutdown();
void RemoveFromList(PgDatabase *pdb, bool persistent);
private:
std::mutex m_Lock;
Handle_t m_Handle;
List<PgDatabase *> m_PermDbs;
};
extern PgDriver g_PgDriver;
unsigned int strncopy(char *dest, const char *src, size_t count);
#endif //_INCLUDE_SM_PGSQL_DRIVER_H_

View File

@ -0,0 +1,350 @@
/**
* vim: set ts=4 :
* =============================================================================
* SourceMod PostgreSQL Extension
* Copyright (C) 2013 AlliedModders LLC. All rights reserved.
* =============================================================================
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 3.0, as published by the
* Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, AlliedModders LLC gives you permission to link the
* code of this program (as well as its derivative works) to "Half-Life 2," the
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
* by the Valve Corporation. You must obey the GNU General Public License in
* all respects for all other code used. Additionally, AlliedModders LLC grants
* this exception to all derivative works. AlliedModders LLC defines further
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
* or <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/
#include "PgStatement.h"
PgStatement::PgStatement(PgDatabase *db, const char* stmtName)
: m_pgsql(db->m_pgsql), m_pParent(db), m_insertID(0), m_affectedRows(0), m_rs(NULL), m_Results(false)
{
m_stmtName = new char[10];
strncopy(m_stmtName, stmtName, 10);
PGresult *desc = PQdescribePrepared(m_pgsql, m_stmtName);
// TODO: Proper error handling?
if (PQresultStatus(desc) != PGRES_COMMAND_OK)
{
PQclear(desc);
return;
}
m_Params = (unsigned int)PQnparams(desc);
if (m_Params)
{
m_pushinfo = (ParamBind *)malloc(sizeof(ParamBind) * m_Params);
memset(m_pushinfo, 0, sizeof(ParamBind) * m_Params);
} else {
m_pushinfo = NULL;
}
m_Results = false;
}
PgStatement::~PgStatement()
{
/* Free result set structures */
if (m_Results)
{
if (m_rs->m_pRes != NULL)
PQclear(m_rs->m_pRes);
delete m_rs;
}
/* Free old blobs */
for (unsigned int i=0; i<m_Params; i++)
{
free(m_pushinfo[i].blob);
}
/* Free our allocated arrays */
free(m_pushinfo);
}
void PgStatement::Destroy()
{
delete this;
}
bool PgStatement::FetchMoreResults()
{
return false;
}
void *PgStatement::CopyBlob(unsigned int param, const void *blobptr, size_t length)
{
void *copy_ptr = NULL;
if (m_pushinfo[param].blob != NULL)
{
if (m_pushinfo[param].length < length)
{
free(m_pushinfo[param].blob);
} else {
copy_ptr = m_pushinfo[param].blob;
}
}
if (copy_ptr == NULL)
{
copy_ptr = malloc(length);
m_pushinfo[param].blob = copy_ptr;
m_pushinfo[param].length = length;
}
memcpy(copy_ptr, blobptr, length);
return copy_ptr;
}
bool PgStatement::BindParamInt(unsigned int param, int num, bool signd)
{
if (param >= m_Params)
{
return false;
}
m_pushinfo[param].data.ival = num;
m_pushinfo[param].type = DBType_Integer;
return true;
}
bool PgStatement::BindParamFloat(unsigned int param, float f)
{
if (param >= m_Params)
{
return false;
}
m_pushinfo[param].data.fval = f;
m_pushinfo[param].type = DBType_Float;
return true;
}
bool PgStatement::BindParamString(unsigned int param, const char *text, bool copy)
{
if (param >= m_Params)
{
return false;
}
const void *final_ptr;
size_t len;
if (copy)
{
len = strlen(text);
final_ptr = CopyBlob(param, text, len+1);
} else {
len = strlen(text);
final_ptr = text;
}
m_pushinfo[param].blob = (void *)final_ptr;
m_pushinfo[param].length = len;
m_pushinfo[param].type = DBType_String;
return true;
}
bool PgStatement::BindParamBlob(unsigned int param, const void *data, size_t length, bool copy)
{
if (param >= m_Params)
{
return false;
}
const void *final_ptr;
if (copy)
{
final_ptr = CopyBlob(param, data, length);
} else {
final_ptr = data;
}
m_pushinfo[param].blob = (void *)final_ptr;
m_pushinfo[param].length = length;
m_pushinfo[param].type = DBType_Blob;
return true;
}
bool PgStatement::BindParamNull(unsigned int param)
{
if (param >= m_Params)
{
return false;
}
m_pushinfo[param].type = DBType_NULL;
return true;
}
bool PgStatement::Execute()
{
/* Clear any past result first! */
if (m_Results)
delete m_rs;
m_Results = false;
PGresult *res;
/* Bind the parameters */
if (m_Params)
{
// Put bound params into a nice array
const char **paramValues = new const char*[m_Params];
int *paramLengths = new int[m_Params];
int *paramFormats = new int[m_Params];
for (unsigned int i=0; i<m_Params; i++)
{
switch (m_pushinfo[i].type)
{
case DBType_Integer:
{
char *buf = new char[64]; // 64 chars should be enough?
snprintf(buf, 64, "%d", m_pushinfo[i].data.ival);
paramValues[i] = buf;
paramLengths[i] = 0;
paramFormats[i] = 0;
break;
}
case DBType_Float:
{
char *buf = new char[64]; // 64 chars should be enough?
snprintf(buf, 64, "%f", m_pushinfo[i].data.fval);
paramValues[i] = buf;
paramLengths[i] = 0;
paramFormats[i] = 0;
break;
}
case DBType_String:
{
paramValues[i] = (char *)m_pushinfo[i].blob;
paramLengths[i] = m_pushinfo[i].length;
paramFormats[i] = 0;
break;
}
case DBType_Blob:
{
paramValues[i] = (char *)m_pushinfo[i].blob;
paramLengths[i] = m_pushinfo[i].length;
paramFormats[i] = 1;
break;
}
case DBType_NULL:
default:
{
paramValues[i] = NULL;
paramLengths[i] = 0;
paramFormats[i] = 0;
break;
}
}
}
res = PQexecPrepared(m_pgsql, m_stmtName, m_Params, paramValues, paramLengths, paramFormats, 0);
delete [] paramFormats;
delete [] paramLengths;
// .. need to free our char buffers
for (unsigned int i=0; i<m_Params; i++)
{
switch (m_pushinfo[i].type)
{
case DBType_Integer:
case DBType_Float:
{
delete paramValues[i];
break;
}
}
}
delete [] paramValues;
}
// There are no parameters to be bound!
else
{
res = PQexecPrepared(m_pgsql, m_stmtName, 0, NULL, NULL, NULL, 0);
}
ExecStatusType status = PQresultStatus(res);
if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK)
{
PQclear(res);
return false;
}
m_affectedRows = (unsigned int)atoi(PQcmdTuples(res));
m_insertID = (unsigned int)PQoidValue(res);
m_pParent->SetLastIDAndRows(m_insertID, m_affectedRows);
/* Skip away if we don't have data */
if (status == PGRES_COMMAND_OK)
{
PQclear(res);
return true;
}
m_rs = new PgBasicResults(res);
m_Results = true;
return true;
}
const char *PgStatement::GetError(int *errCode/* =NULL */)
{
if (m_Results)
{
if (errCode)
{
// PostgreSQL only supports SQLSTATE error codes.
// https://www.postgresql.org/docs/9.6/errcodes-appendix.html
*errCode = -1;
}
return PQresultErrorMessage(m_rs->m_pRes);
}
else
{
return PQerrorMessage(m_pgsql);
}
}
unsigned int PgStatement::GetAffectedRows()
{
return m_affectedRows;
}
unsigned int PgStatement::GetInsertID()
{
return m_insertID;
}
IResultSet *PgStatement::GetResultSet()
{
return (m_Results ? m_rs : NULL);
}

View File

@ -0,0 +1,85 @@
/**
* vim: set ts=4 :
* =============================================================================
* SourceMod PostgreSQL Extension
* Copyright (C) 2013 AlliedModders LLC. All rights reserved.
* =============================================================================
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 3.0, as published by the
* Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, AlliedModders LLC gives you permission to link the
* code of this program (as well as its derivative works) to "Half-Life 2," the
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
* by the Valve Corporation. You must obey the GNU General Public License in
* all respects for all other code used. Additionally, AlliedModders LLC grants
* this exception to all derivative works. AlliedModders LLC defines further
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
* or <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/
#ifndef _INCLUDE_SM_PGSQL_STATEMENT_H_
#define _INCLUDE_SM_PGSQL_STATEMENT_H_
#include "PgDatabase.h"
#include "PgBasicResults.h"
struct ParamBind
{
union
{
float fval;
int ival;
} data;
void *blob;
size_t length;
DBType type;
};
class PgStatement : public IPreparedQuery
{
public:
PgStatement(PgDatabase *db, const char* stmtName);
~PgStatement();
public: //IQuery
IResultSet *GetResultSet();
bool FetchMoreResults();
void Destroy();
public: //IPreparedQuery
bool BindParamInt(unsigned int param, int num, bool signd=true);
bool BindParamFloat(unsigned int param, float f);
bool BindParamNull(unsigned int param);
bool BindParamString(unsigned int param, const char *text, bool copy);
bool BindParamBlob(unsigned int param, const void *data, size_t length, bool copy);
bool Execute();
const char *GetError(int *errCode=NULL);
unsigned int GetAffectedRows();
unsigned int GetInsertID();
private:
void *CopyBlob(unsigned int param, const void *blobptr, size_t length);
private:
PGconn *m_pgsql;
ke::RefPtr<PgDatabase> m_pParent;
char *m_stmtName;
ParamBind *m_pushinfo;
unsigned int m_Params;
unsigned int m_insertID;
unsigned int m_affectedRows;
PgBasicResults *m_rs;
bool m_Results;
};
#endif //_INCLUDE_SM_PGSQL_STATEMENT_H_

View File

@ -0,0 +1,70 @@
/*-------------------------------------------------------------------------
*
* postgres_ext.h
*
* This file contains declarations of things that are visible everywhere
* in PostgreSQL *and* are visible to clients of frontend interface libraries.
* For example, the Oid type is part of the API of libpq and other libraries.
*
* Declarations which are specific to a particular interface should
* go in the header file for that interface (such as libpq-fe.h). This
* file is only for fundamental Postgres declarations.
*
* User-written C functions don't count as "external to Postgres."
* Those function much as local modifications to the backend itself, and
* use header files that are otherwise internal to Postgres to interface
* with the backend.
*
* src/include/postgres_ext.h
*
*-------------------------------------------------------------------------
*/
#ifndef POSTGRES_EXT_H
#define POSTGRES_EXT_H
#include "pg_config_ext.h"
/*
* Object ID is a fundamental type in Postgres.
*/
typedef unsigned int Oid;
#ifdef __cplusplus
#define InvalidOid (Oid(0))
#else
#define InvalidOid ((Oid) 0)
#endif
#define OID_MAX UINT_MAX
/* you will need to include <limits.h> to use the above #define */
/* Define a signed 64-bit integer type for use in client API declarations. */
typedef PG_INT64_TYPE pg_int64;
/*
* Identifiers of error message fields. Kept here to keep common
* between frontend and backend, and also to export them to libpq
* applications.
*/
#define PG_DIAG_SEVERITY 'S'
#define PG_DIAG_SEVERITY_NONLOCALIZED 'V'
#define PG_DIAG_SQLSTATE 'C'
#define PG_DIAG_MESSAGE_PRIMARY 'M'
#define PG_DIAG_MESSAGE_DETAIL 'D'
#define PG_DIAG_MESSAGE_HINT 'H'
#define PG_DIAG_STATEMENT_POSITION 'P'
#define PG_DIAG_INTERNAL_POSITION 'p'
#define PG_DIAG_INTERNAL_QUERY 'q'
#define PG_DIAG_CONTEXT 'W'
#define PG_DIAG_SCHEMA_NAME 's'
#define PG_DIAG_TABLE_NAME 't'
#define PG_DIAG_COLUMN_NAME 'c'
#define PG_DIAG_DATATYPE_NAME 'd'
#define PG_DIAG_CONSTRAINT_NAME 'n'
#define PG_DIAG_SOURCE_FILE 'F'
#define PG_DIAG_SOURCE_LINE 'L'
#define PG_DIAG_SOURCE_FUNCTION 'R'
#endif /* POSTGRES_EXT_H */

View File

@ -0,0 +1,72 @@
/**
* vim: set ts=4 :
* =============================================================================
* SourceMod PostgreSQL Extension
* Copyright (C) 2013 AlliedModders LLC. All rights reserved.
* =============================================================================
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 3.0, as published by the
* Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, AlliedModders LLC gives you permission to link the
* code of this program (as well as its derivative works) to "Half-Life 2," the
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
* by the Valve Corporation. You must obey the GNU General Public License in
* all respects for all other code used. Additionally, AlliedModders LLC grants
* this exception to all derivative works. AlliedModders LLC defines further
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
* or <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/
#ifndef _INCLUDE_SOURCEMOD_EXTENSION_CONFIG_H_
#define _INCLUDE_SOURCEMOD_EXTENSION_CONFIG_H_
/**
* @file smsdk_config.h
* @brief Contains macros for configuring basic extension information.
*/
/* Basic information exposed publicly */
#define SMEXT_CONF_NAME "PostgreSQL-DBI"
#define SMEXT_CONF_DESCRIPTION "PostgreSQL driver implementation for DBI"
#define SMEXT_CONF_VERSION ""
#define SMEXT_CONF_AUTHOR "AlliedModders LLC"
#define SMEXT_CONF_URL "http://www.sourcemod.net/"
#define SMEXT_CONF_LOGTAG "PGSQL"
#define SMEXT_CONF_LICENSE "GPL"
#define SMEXT_CONF_DATESTRING ""
/**
* @brief Exposes plugin's main interface.
*/
#define SMEXT_LINK(name) SDKExtension *g_pExtensionIface = name;
/**
* @brief Sets whether or not this plugin required Metamod.
* NOTE: Uncomment to enable, comment to disable.
*/
//#define SMEXT_CONF_METAMOD
/** Enable interfaces you want to use here by uncommenting lines */
//#define SMEXT_ENABLE_FORWARDSYS
//#define SMEXT_ENABLE_HANDLESYS
//#define SMEXT_ENABLE_PLAYERHELPERS
#define SMEXT_ENABLE_DBMANAGER
//#define SMEXT_ENABLE_GAMECONF
//#define SMEXT_ENABLE_MEMUTILS
//#define SMEXT_ENABLE_GAMEHELPERS
//#define SMEXT_ENABLE_TIMERSYS
//#define SMEXT_ENABLE_THREADER
#endif // _INCLUDE_SOURCEMOD_EXTENSION_CONFIG_H_

104
extensions/pgsql/version.rc Normal file
View File

@ -0,0 +1,104 @@
// Microsoft Visual C++ generated resource script.
//
//#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
#include <sourcemod_version.h>
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// English (U.S.) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
#ifdef _WIN32
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
#endif //_WIN32
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION SM_VERSION_FILE
PRODUCTVERSION SM_VERSION_FILE
FILEFLAGSMASK 0x17L
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x4L
FILETYPE 0x2L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "000004b0"
BEGIN
VALUE "Comments", "PostgreSQL Extension"
VALUE "FileDescription", "SourceMod PostgreSQL Extension"
VALUE "FileVersion", SM_VERSION_STRING
VALUE "InternalName", "SourceMod PostgreSQL Extension"
VALUE "LegalCopyright", "Copyright (c) 2004-2013, AlliedModders LLC"
VALUE "OriginalFilename", BINARY_NAME
VALUE "ProductName", "SourceMod PostgreSQL Extension"
VALUE "ProductVersion", SM_VERSION_STRING
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x0, 1200
END
END
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
#endif // English (U.S.) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED

View File

@ -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.");
}