Pooled PostgreSQL database
by Danni · 06/30/2008 (1:15 pm) · 17 comments
Download Code File
Foreword:
Thanks to Tony "SgtFlame" Richards and the Event Driven Database resource, I modeled some of the threading code around that resource. Kindly let me know if you spot any bugs.
What is PostgreSQL?
PostgreSQL is a real RDBMS SQL server that "is highly scalable both in the sheer quantity of data it can manage and in the number of concurrent users it can accommodate. There are active PostgreSQL systems in production environments that manage in excess of 4 terabytes of data". Postgres has been quoted as "not only as fast or faster than MySQL, but almost as fast as Oracle" which is the software many organizations, including banks, use for storing data.
More information about PostgresSQL can be found here www.postgresql.org/about/
What about SQLite?
Comparing SQLite to PostgresSQL is not really a fair comparison as SQLite is more of a local data storage system that just happens to use SQL as an interface for storing and retrieving data. It is faster simply due to the lack of the TCP/IP stack overhead in RDBMS servers, it cannot scale or accept concurrent requests for data like true RDBMS can. Their own website explains this much better www.sqlite.org/cvstrac/wiki?p=WhenToUseSqlite
Installation:
- Install PostgreSQL: www.postgresql.org/
- Extract the files in pgSQL.zip to your engine folder.
- Ensure the PostgreSQL library and include folder are in the build environment.
- Add the functions in the code block below to core/llist.h.
- Add libpq.lib to the linker dependencies.
- Copy the PostgreSQL DLL's to the game folder (libpq.dll, ssleay32.dll, libintl-2.dll, krb5_32.dll, libiconv-2.dll).
- Build!
- Add the initializing code somewhere useful. I use the createServer TorqueScript function.
- After the first run, the connection information variables are placed in the server's pref.cs files, add your credentials here.
core/llist.h optimizations
Example initializing code.
Now go create your database enabled app! :D
Pool API
%db = PGSQLPool::getPool();
Obtain the handle to the query pool.
%db.Exec([%object], %callbackName, "SQL Query;", ...)
WARNING!! NEVER EVER CONCATENATE VARIABLES INTO THE SQL QUERY!! USE THE PROVIDED INTERFACE. IGNORING THIS WARNING WILL RESULT IN SQL INJECTION VULNERABLITIES IN YOUR GAME.
When building your query, you can omit the space where you would insert variables in favor of one of the replacement tokens. These are;
%s Normal Strings
%b Binary Strings
%x Everything else, including Real's and Integers (as a string)
Then add the actual variables to the variable argument list. %s and %b will automatically apply quotes.
E.g.
Vulnerable:
Safe:
Even with this tool you should still validate client input as good security practice.
Result API
%num = %db.getValue(%resultset, %row, %fieldnum)
Get the value of a row and field in the result set.
%num = %db.getNamedValue(%resultset, %row, %fieldname)
Get the value of a row and field in the result set by field name.
%num = %db.getNumRows(%resultset)
Get the number of rows in a result set.
%num = %db.getNumFields(%resultset)
Get the number of fields in a result set.
%name = %db.getFieldName(%resultset, %fieldnum)
Get the name of a field in a result set.
%num = %db.getFieldNum(%resultset, %fieldname)
Get the number of a field in a result set by name.
Use and abuse
Here are some example usages;
Object in TorqueScript
Function in TorqueScript
Foreword:
Thanks to Tony "SgtFlame" Richards and the Event Driven Database resource, I modeled some of the threading code around that resource. Kindly let me know if you spot any bugs.
What is PostgreSQL?
PostgreSQL is a real RDBMS SQL server that "is highly scalable both in the sheer quantity of data it can manage and in the number of concurrent users it can accommodate. There are active PostgreSQL systems in production environments that manage in excess of 4 terabytes of data". Postgres has been quoted as "not only as fast or faster than MySQL, but almost as fast as Oracle" which is the software many organizations, including banks, use for storing data.
More information about PostgresSQL can be found here www.postgresql.org/about/
What about SQLite?
Comparing SQLite to PostgresSQL is not really a fair comparison as SQLite is more of a local data storage system that just happens to use SQL as an interface for storing and retrieving data. It is faster simply due to the lack of the TCP/IP stack overhead in RDBMS servers, it cannot scale or accept concurrent requests for data like true RDBMS can. Their own website explains this much better www.sqlite.org/cvstrac/wiki?p=WhenToUseSqlite
Installation:
- Install PostgreSQL: www.postgresql.org/
- Extract the files in pgSQL.zip to your engine folder.
- Ensure the PostgreSQL library and include folder are in the build environment.
- Add the functions in the code block below to core/llist.h.
- Add libpq.lib to the linker dependencies.
- Copy the PostgreSQL DLL's to the game folder (libpq.dll, ssleay32.dll, libintl-2.dll, krb5_32.dll, libiconv-2.dll).
- Build!
- Add the initializing code somewhere useful. I use the createServer TorqueScript function.
- After the first run, the connection information variables are placed in the server's pref.cs files, add your credentials here.
core/llist.h optimizations
T *pop_first(void)
{
if (!first_entry)
return NULL;
T* e = first_entry->Data;
LListNode<T> *entryNode = first_entry;
first_entry = first_entry->Next;
if( entryNode->Next == NULL )
{
last_entry = entryNode->Prev;
}
else
{
entryNode->Next->Prev = entryNode->Prev;
}
delete entryNode;
--cnt;
return e;
}
T *pop_last(void)
{
if (!last_entry)
return NULL;
T* e = last_entry->Data;
LListNode<T> *entryNode = last_entry;
if( entryNode->Prev == NULL )
{
first_entry = entryNode->Next;
}
else
{
entryNode->Prev->Next = entryNode->Next;
}
last_entry = entryNode->Prev;
delete entryNode;
--cnt;
return e;
}
void push_back(T &entry)
{
T *newEntry = new T;
*newEntry = entry;
LListNode<T> *prevNode = NULL;
LListNode<T> *newNode = new LListNode<T>;
newNode->Data = newEntry;
prevNode = last_entry;
last_entry = newNode;
newNode->Next = NULL;
newNode->Prev = prevNode;
if (!first_entry)
first_entry = last_entry;
++cnt;
}
//---------------------------------------------------------------------------------------
// Unlink node from list (without destroying it)
//---------------------------------------------------------------------------------------
void unlink(LListNode<T> *entryNode)
{
if( !entryNode ) return;
if( entryNode->Prev == NULL )
{
first_entry = entryNode->Next;
}
else
{
entryNode->Prev->Next = entryNode->Next;
}
if( entryNode->Next == NULL )
{
last_entry = entryNode->Prev;
}
else
{
entryNode->Next->Prev = entryNode->Prev;
}
delete entryNode;
--cnt;
}
void free(LListNode<T> *entry)
{
unlink(entry);
delete entry;
}Example initializing code.
function createServer(%serverType, %mission)
{
if (%mission $= "") {
error("createServer: mission name unspecified");
return;
}
// Make sure our mission name is relative so that it can send
// across the network correctly
%mission = makeRelativePath(%mission, getWorkingDirectory());
[b] PGSQLPool::createPool();[/b]
destroyServer();
...Now go create your database enabled app! :D
Pool API
%db = PGSQLPool::getPool();
Obtain the handle to the query pool.
%db.Exec([%object], %callbackName, "SQL Query;", ...)
WARNING!! NEVER EVER CONCATENATE VARIABLES INTO THE SQL QUERY!! USE THE PROVIDED INTERFACE. IGNORING THIS WARNING WILL RESULT IN SQL INJECTION VULNERABLITIES IN YOUR GAME.
When building your query, you can omit the space where you would insert variables in favor of one of the replacement tokens. These are;
%s Normal Strings
%b Binary Strings
%x Everything else, including Real's and Integers (as a string)
Then add the actual variables to the variable argument list. %s and %b will automatically apply quotes.
E.g.
Vulnerable:
%db.Exec(%client, "onPlayerListCallback", "SELECT * FROM players WHERE uid = " @ %client.uid @ " AND name = \"" @ %client.name @ "\"";");
Safe:
%db.Exec(%client, "onPlayerListCallback", "SELECT * FROM players WHERE uid = %x AND name = %s;", %client.uid, %client.name);
Even with this tool you should still validate client input as good security practice.
Result API
%num = %db.getValue(%resultset, %row, %fieldnum)
Get the value of a row and field in the result set.
%num = %db.getNamedValue(%resultset, %row, %fieldname)
Get the value of a row and field in the result set by field name.
%num = %db.getNumRows(%resultset)
Get the number of rows in a result set.
%num = %db.getNumFields(%resultset)
Get the number of fields in a result set.
%name = %db.getFieldName(%resultset, %fieldnum)
Get the name of a field in a result set.
%num = %db.getFieldNum(%resultset, %fieldname)
Get the number of a field in a result set by name.
Use and abuse
Here are some example usages;
Object in TorqueScript
function GameConnection::sendAvailablePlayers( %client )
{
%db = PGSQLPool::getPool();
%db.Exec(%client, "onPlayerListCallback", "SELECT * FROM players WHERE uid = %x;", %client.uid);
}
function GameConnection::onPlayerListCallback( %client, %r )
{
commandToClient(%client, 'availablePlayers');
%db = PGSQLPool::getPool();
%num = %db.getNumRows(%r);
for (%i = 0; %i < %num; %i++)
{
%pid = %db.getNamedValue(%r,%i,"pid");
%name = %db.getNamedValue(%r,%i,"name");
%class = %db.getNamedValue(%r,%i,"class");
%level = %db.getNamedValue(%r,%i,"level");
if (%pid)
commandToClient(%client, 'sendPlayer', %pid, %name, %class, %level);
}
}Function in TorqueScript
function onRegister( %user, %pass, %email )
{
%db = PGSQLPool::getPool();
%db.Exec("onRegisterCallback", "SELECT addUser(%s, %s, %s);", %user, %pass, %email);
}
function onRegisterCallback( %r )
{
%db = PGSQLPool::getPool();
%uid = %db.getValue(%r,0,0);
if (%uid)
{
// do stuff
}
}
#2
@Nathan: If you are thinking backup / restore, you can read more about the possible options on the PostgreSQL docs site.
06/30/2008 (6:20 pm)
Great resource! Thank you!@Nathan: If you are thinking backup / restore, you can read more about the possible options on the PostgreSQL docs site.
#3
@Nathan: You can backup and restore the database, you can also run concurrent versions on different servers an use asynchronous replication so if one crashes you fail over to the other and nothing is lost. Using true RDBMS, if your app is ready for it, can open up a world of possibilities.
06/30/2008 (9:49 pm)
Cheers, I updated the resource with a bit more information about PGSQL so others understand it is nothing like SQLite. @Nathan: You can backup and restore the database, you can also run concurrent versions on different servers an use asynchronous replication so if one crashes you fail over to the other and nothing is lost. Using true RDBMS, if your app is ready for it, can open up a world of possibilities.
#4
07/01/2008 (2:08 am)
Is the support for saving already exposed to TorqueScript? I don't want to sound lazy, but it would be great if it is, but I could add it myself.
#5
07/01/2008 (3:26 am)
It doesn't work that way. The SQL server handles the actual data storage, this just provides a means of accessing the SQL server.
#6
07/01/2008 (1:28 pm)
Is there a way to access the server and tell it to store the data in a file through TorqueScript?
#7
07/01/2008 (1:50 pm)
@Nathan: Yes. Think of the resource as a means of communicating with your db server, just as you would from the server console - except from TorqueScript. You can do anything once you're connected.
#8
07/01/2008 (3:47 pm)
Oh, so the exec command isn't like the SQLite resource's querying command, its a way to fully control the server. I got it, thanks!
#9
07/01/2008 (4:49 pm)
Sure. I'm not familiar with SQLite, so I can't tell what the difference is exactly, but generally you'd want to this kind of data management for persistent multiplayer games and/or rankings, and SQLite for others where a remote centralized persistent data storage is not required.
#10
Thanks again ... and my team thanks you as well.
[EDIT]
If you want to embed the DB Authentication stuff inside of the exe file instead of having it in the prefs.cs file then you cab do the following.
1). Add the DB Authentication stuff by modifying the following code in pgSQL/pgsqlDatabase.cpp
2). Comment out the parts that actually write the entries into the prefs.cs file also in pgSQL/pgsqlDatabase.cpp.
It is important to note that you will need to recompile your EXE in order to use this and re-compile it if you change DB Authentication aspects.
07/22/2008 (8:08 pm)
Awesome resource Danni. I gave you a 5. This really made my life easier as my team was putting pressure on me to get the Postgres stuff done for Soul Wars.Thanks again ... and my team thanks you as well.
[EDIT]
If you want to embed the DB Authentication stuff inside of the exe file instead of having it in the prefs.cs file then you cab do the following.
1). Add the DB Authentication stuff by modifying the following code in pgSQL/pgsqlDatabase.cpp
// Static Members char * PGSQLPool::mHost = "[b]HOST_NAME[/b]"; char * PGSQLPool::mPort = "5432"; char * PGSQLPool::mUser = "[b]USER_NAME[/b]"; char * PGSQLPool::mDatabase = "[b]DATABASE_NAME[/b]"; char * PGSQLPool::mPassword = "[b]PASSWORD[/b]";
2). Comment out the parts that actually write the entries into the prefs.cs file also in pgSQL/pgsqlDatabase.cpp.
void PGSQLPool::consoleInit()
{
[b]//Con::addVariable("$pref::Server::Database::Password", TypeString, &mPassword);[/b]
[b]//Con::addVariable("$pref::Server::Database::Host", TypeString, &mHost);[/b]
[b]//Con::addVariable("$pref::Server::Database::Port", TypeString, &mPort);[/b]
[b]//Con::addVariable("$pref::Server::Database::Database", TypeString, &mDatabase);[/b]
[b]//Con::addVariable("$pref::Server::Database::Threads", TypeS32, &mThreads);[/b]
[b]//Con::addVariable("$pref::Server::Database::Username", TypeString, &mUser);[/b]
}It is important to note that you will need to recompile your EXE in order to use this and re-compile it if you change DB Authentication aspects.
#11
Let me know if you have any trouble.
07/30/2008 (11:53 pm)
That works. I would be careful though, if you distribute the exe then someone has your authentication credentials.Let me know if you have any trouble.
#12
We are taking a different approach with our authentication stuff which involves a completely separate server codebase from the client due to the nature of our project. This resource is fundamental in our project. The client-server seperation has already started with us removing the ability to provide mods to our game and cleaning the underlying game structure to only have client/ data/ and server/ folders (obviously there is still the profile/ and shader/ folder).
The next step is to separate the Client and Server completely and then use this resource. What we are doing is reading our DB credentials from a config file on our server which then populates the prefs file and is in turn used by this resource. Some of my team are paranoid and prefer relying on a FILESYSTEM for security. :)
07/31/2008 (8:01 am)
@Danni ... yes sir you are right. Some people may want to do that ... you never know. :)We are taking a different approach with our authentication stuff which involves a completely separate server codebase from the client due to the nature of our project. This resource is fundamental in our project. The client-server seperation has already started with us removing the ability to provide mods to our game and cleaning the underlying game structure to only have client/ data/ and server/ folders (obviously there is still the profile/ and shader/ folder).
The next step is to separate the Client and Server completely and then use this resource. What we are doing is reading our DB credentials from a config file on our server which then populates the prefs file and is in turn used by this resource. Some of my team are paranoid and prefer relying on a FILESYSTEM for security. :)
#13
08/22/2008 (4:45 am)
Is there a way to use this for local only games such that the player does not have to install and run a pgsql server?
#14
08/22/2008 (5:44 am)
@James: You'd probably want to use SQLite for that.
#15
I am looking for the "llist.h" file to insert the functions...
Can u help me to find that file?
10/23/2009 (12:25 am)
Hi there,I am looking for the "llist.h" file to insert the functions...
Can u help me to find that file?
#16
In addition the thread constructor needs to be changed like this, at leeast that's working for me.
PGSQLThread::PGSQLThread(const char* host, const char* port, const char* _db, const char* user, const char* pass, PGSQLPool *m, U8 _id)
{
db = NULL;
mHost = host;
mPort = port;
mDatabase = _db;
mUser = user;
mPassword = pass;
id = _id;
mStop = false;
master = m;
mThread = new Thread((ThreadRunFunction)processThread, this, false); // Changed from true
mThread->start(); // added
}
10/27/2009 (12:26 am)
T3D doesn't use that file, I found the copy of it I'm using in a TGEA 1.7.1 install.In addition the thread constructor needs to be changed like this, at leeast that's working for me.
PGSQLThread::PGSQLThread(const char* host, const char* port, const char* _db, const char* user, const char* pass, PGSQLPool *m, U8 _id)
{
db = NULL;
mHost = host;
mPort = port;
mDatabase = _db;
mUser = user;
mPassword = pass;
id = _id;
mStop = false;
master = m;
mThread = new Thread((ThreadRunFunction)processThread, this, false); // Changed from true
mThread->start(); // added
}
#17
1. [DONE] Install PostgreSQL: www.postgresql.org
2. [DONE] Extract the files in pgSQL.zip to your engine folder.
3. Ensure the PostgreSQL library and include folder are in the build environment.
-> ".../My Projects/PROJECTNAME/buildFiles": Is that correct?
4. Add the functions in the code block below to core/llist.h.
-> Where I have to insert the code in T3d?
5. [DONE I THINK]Add libpq.lib to the linker dependencies.
-> libpq.png
-> Right?
6. [DONE] Copy the PostgreSQL DLL's to the game folder (libpq.dll, ssleay32.dll, libintl-2.dll, krb5_32.dll, libiconv-2.dll).
-> Copied to ".../My Projects/PROJECTNAME/game"
11/03/2009 (11:48 pm)
step by step pls :)1. [DONE] Install PostgreSQL: www.postgresql.org
2. [DONE] Extract the files in pgSQL.zip to your engine folder.
3. Ensure the PostgreSQL library and include folder are in the build environment.
-> ".../My Projects/PROJECTNAME/buildFiles": Is that correct?
4. Add the functions in the code block below to core/llist.h.
-> Where I have to insert the code in T3d?
5. [DONE I THINK]Add libpq.lib to the linker dependencies.
-> libpq.png
-> Right?
6. [DONE] Copy the PostgreSQL DLL's to the game folder (libpq.dll, ssleay32.dll, libintl-2.dll, krb5_32.dll, libiconv-2.dll).
-> Copied to ".../My Projects/PROJECTNAME/game"
Torque Owner Nathan Kent