Showing posts with label sqlite. Show all posts
Showing posts with label sqlite. Show all posts

Thursday, May 26, 2011

Using SQLite as a data store in C++

There are various advantages in a using a SQLite as a database with the best being that it’s small, fast and reliable.

The best way to use SQLite is to embed it in the game using the recommended amalgamation source code available on sqlite.org, but it is also available as a library for Windows (DLL) or Linux (shared-library .so ) .

From a licensing point of view, using SQLite in any commercial game is also VERY safe since SQLite’s source has been released into the public domain.

The following steps should allow C++ programmers to embed SQLite into their games.

Step 1

Go to http://www.sqlite.org and click on Download and grab a file named sqlite-amalgamation-X_X_X.zip where the X’s are replaced by the appropriate version number. In my case I downloaded sqlite-amalgamation-3_7_0.zip (Windows) or sqlite-amalgamation-3.7.0.tar.gz (Linux)

Step 2
Extract the downloaded file and add the sqlite3.h and sqlite3.c files to the project. The size of the sqlite3.c file should be really big, around 3.96MB.

Step 3
Compile sqlite3.c in order to create a object file for it. The object file should be around 1.09MB.

Step 4
Include the sqlite3.h file in the game’s c++ files. For demonstration purposes I created a main.cpp file that looks as follows:

#include <stdio.h>
#include <cstdlib>
#include "sqlite3/sqlite3.h"
#include <string>
using namespace std;
int main(void){
sqlite3 *db;
char *zErrMsg = 0;
int rc;
string q = "";
string dbName = "game.dat";
//Attempt to open database. If it does not exist create it.
rc = sqlite3_open(dbName.c_str(), &db);
if( rc ){
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
exit(1);
}
//Set query to execute
q = "";
sqlite3_close(db); //Always close db when done with it
return 0;
}

Compile and run. There should now be a file called game.dat in the debug directory since SQLite will create the database file by default if it is not found.

Step 5
Since it is now possible to create a database it is time to create some tables. For demonstration purposes I created 2 tables. t_type and t_assets.

The Types table will simply contain information about various types of assets. The table will consist of an id for identification purposes, a parent id in order to be able to create a tree hierarchy and a title for what type it is, e.g. weapon, vehicle, npc, etc…

The Assets table will contain information about a specific asset such as an image file or 3d object. The table consist of an id, a type id, src or location of the asset, title or name of the asset and a description. E.g. Image of a “Toyota Bakkie” that is located at /assests/vehicles/toyota.png with the description:”A Red 4×4 Toyota Bakkie”.

In order to execute queries on the database the sqlite3_exec function will be used. The code to do that follows:

//Set query to execute
q = "CREATE TABLE t_type"
"("
"'ID' INTEGER PRIMARY KEY AUTOINCREMENT,"
"'PARENT_ID' INTEGER,"
"'TITLE' TEXT" ");"
"CREATE TABLE t_assets"
"("
"'ID' INTEGER PRIMARY KEY AUTOINCREMENT,"
"'TYPE_ID' INTEGER,"
"'SRC' TEXT,"
"'TITLE' TEXT,"
"'DESCRIPTION' TEXT"
");";
rc = sqlite3_exec(db, q.c_str(), NULL, 0, &zErrMsg);
if( rc!=SQLITE_OK ){
fprintf(stderr, "SQL error: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
}

Note that I do not test whether a table actually exist, maybe you can add that to your code as a bit of an exercise?

Step 6
It is now time to insert some data. The query to do that follows:

q = "INSERT INTO t_type('PARENT_ID','TITLE') VALUES (0,'VEHICLES');"
"INSERT INTO t_type('PARENT_ID','TITLE') VALUES (0,'ANIMALS');"
"INSERT INTO t_assets('TYPE_ID','SRC','TITLE','DESCRIPTION') VALUES (1,'/assets/vechicles/toyota.png','A Red 4x4 Toyota Bakkie');"
"INSERT INTO t_assets('TYPE_ID','SRC','TITLE','DESCRIPTION') VALUES (1,'/assets/vechicles/ferrari.png','A twin-turbo F50 Ferrari, like the one John Carmack drives');"
"INSERT INTO t_assets('TYPE_ID','SRC','TITLE','DESCRIPTION') VALUES (2,'/assets/vechicles/munky.png','A munky. It is a bloody munky');";

You can of course do more advanced stuff like getting the ID of the types and then use that in the query.

A Fast way to check the data in the file is to open it with notepad or notepad++ to check the results; but I will not recommend that approach with big files.

Step 7
In order to query the data, a callback function needs to be declared. The declaration for that looks as follows:

static int callback(void*, int, char**, char**);

This callback function will be called for each row returned from the query.

The first parameter of this function is not really used.
The second parameter contains the number of columns.
The third parameter is an array containing the value of each column as a char*.
The Fourth and last parameter contains the Names of each column.

An example callback function follows:

static int callback(void *NotUsed, int argc, char **argv, char **azColName){
int i;
for(i=0; i<argc; i++){
printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
}
printf("\n");
return 0;
}

And this is how it will be used:

q = "SELECT * FROM t_assets WHERE TYPE_ID=1;";
rc = sqlite3_exec(db, q.c_str(), callback, 0, &zErrMsg);
if( rc!=SQLITE_OK ){
fprintf(stderr, "SQL error: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
}

Expected Output:

And that is it..The full source code for main.cpp looks as follows:

#include <stdio.h>
#include <conio.h>
#include <cstdlib>
#include "sqlite3/sqlite3.h"
#include <string>

using namespace std;

static int callback(void *NotUsed, int argc, char **argv, char **azColName){

int i;

for(i=0; i<argc; i++){
printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
}

printf("\n");
return 0;
}

int main(void){
sqlite3 *db;
char *zErrMsg = 0;
int rc;
string q = "";
string dbName = "game.dat";

//Attempt to open database. If it does not exist create it.
rc = sqlite3_open(dbName.c_str(), &db);

if( rc ){
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
exit(1);
}

//Set query to execute
q = "CREATE TABLE t_type"
"("
"'ID' INTEGER PRIMARY KEY AUTOINCREMENT,"
"'PARENT_ID' INTEGER KEY,"
"'TITLE' TEXT"
");"
"CREATE TABLE t_assets"
"("
"'ID' INTEGER PRIMARY KEY AUTOINCREMENT,"
"'TYPE_ID' INTEGER KEY,"
"'SRC' TEXT,"
"'TITLE' TEXT,"
"'DESCRIPTION' TEXT"
");";

rc = sqlite3_exec(db, q.c_str(), NULL, 0, &zErrMsg);
if( rc!=SQLITE_OK ){
fprintf(stderr, "SQL error: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
}

q = "INSERT INTO t_type('PARENT_ID','TITLE') VALUES (0,'VEHICLES');"
"INSERT INTO t_type('PARENT_ID','TITLE') VALUES (0,'ANIMALS');"
"INSERT INTO t_assets('TYPE_ID','SRC','TITLE','DESCRIPTION') VALUES (1,'/assets/vechicles/toyota.png','Toyota Bakkie','A Red 4x4 Toyota Bakkie');"
"INSERT INTO t_assets('TYPE_ID','SRC','TITLE','DESCRIPTION') VALUES (1,'/assets/vechicles/ferrari.png','Ferrari','A twin-turbo F50 Ferrari, like the one John Carmack drives');"
"INSERT INTO t_assets('TYPE_ID','SRC','TITLE','DESCRIPTION') VALUES (2,'/assets/vechicles/munky.png','Munky','A munky. It is a bloody munky');";

rc = sqlite3_exec(db, q.c_str(), NULL, 0, &zErrMsg);

if( rc!=SQLITE_OK ){
fprintf(stderr, "SQL error: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
}

q = "SELECT * FROM t_assets WHERE TYPE_ID=1;";
rc = sqlite3_exec(db, q.c_str(), callback, 0, &zErrMsg);

if( rc!=SQLITE_OK ){
fprintf(stderr, "SQL error: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
}

sqlite3_close(db); //Always close db when done with it
printf("\nPress Enter.");
getch();

return 0;
}

Thursday, March 13, 2008

SQLite and SOCI

Now that I know that I can do SQL queries agains the SQLite database, my first objective is to create a C++ dataobject that will allow my yet to be created framework to interact with the database. But first I need to be able to actually access SQLite through code.

My search led me to SOCI which is a C++ Database Access Library. I quote from their website:

The idea is to provide C++ programmers a way to access SQL databases in the most natural and intuitive way.
From what I can see SOCI looks to be the best thing for what I want to achieve.

http://soci.sourceforge.net

Thursday, February 7, 2008

SQLite - Updating Tables

What if I need to make changes to the data in my table? As an example, I forgot to insert Armourette's birthday and Johan is not active yet. This mean I will have to run an update query on the table.

SQLite query:

SQLite version 3.5.4
Enter ".help" for instructions
sqlite> .schema person
CREATE TABLE person
(
'ID' INTEGER PRIMARY KEY AUTOINCREMENT,
'FIRSTNAMES' TEXT,
'SURNAME' TEXT,
'NAME' TEXT,
'BIRTHDATE' DEFAULT CURRENT_DATE,
'TITLE' TEXT,
'ACTIVE' TEXT(1) DEFAULT 'N'
);
sqlite> select * from person;
1|Johannes|Strydom|Johan|1977-02-22|Mr|N
2|Armourette|Strydom|Armourette|2008-01-17|Mrs|Y
sqlite> UPDATE person SET ACTIVE='Y' WHERE ID=1;
sqlite> UPDATE person SET BIRTHDATE='1983-07-05' WHERE ID=2;
sqlite> select * from person;
1|Johannes|Strydom|Johan|1977-02-22|Mr|Y
2|Armourette|Strydom|Armourette|1983-07-05|Mrs|Y
sqlite>
Using the primary key ID I am able to quickly update the correct data.

Wednesday, January 16, 2008

SQLite - Creating Tables

What is a database without tables?

My next chalenge in SQLite is to create tables. Using the SQLite create table documentation I am able to create tables without any hassle.

Say for example I want to store data about a person in the database and to do that I want to create a table with fields for firstnames, surname, preferred name, birthdate,title and some sort of primary reference that should be indexed and that should auto increment whenever a new row is added to the table. I also want to indicate in this table whether the person is active.

So I start up SQLite and attached my database;
SQLite version 3.5.4
Enter ".help" for instructions
sqlite> ATTACH DATABASE 'myDB.fl' AS myDB;
sqlite> .databases
Ok so I now have everything in place to start creating the table.
sqlite> CREATE TABLE myDB.person
...> (
...> 'ID' INTEGER PRIMARY KEY AUTOINCREMENT,
...> 'FIRSTNAMES' TEXT,
...> 'SURNAME' TEXT,
...> 'NAME' TEXT,
...> 'BIRTHDATE' DEFAULT CURRENT_DATE,
...> 'TITLE' TEXT,
...> 'ACTIVE' TEXT(1) DEFAULT 'N'
...> );
Fine so far. Now to insert data!
sqlite> INSERT INTO myDB.person('FIRSTNAMES','SURNAME','NAME','BIRTHDATE','TITLE
') VALUES ('Johannes','Strydom','Johan','1977-02-22','Mr');
No problemo. To see whether everything works I do a SELECT
sqlite> SELECT * FROM myDB.person;
1|Johannes|Strydom|Johan|1977-02-22|Mr|N
And another insert to test the default date.
sqlite> INSERT INTO myDB.person('FIRSTNAMES','SURNAME','NAME','TITLE'
,'ACTIVE') VALUES ('Armourette','Strydom','Armourette','Mrs','Y');
sqlite> SELECT * FROM myDB.person;
1|Johannes|Strydom|Johan|1977-02-22|Mr|N
2|Armourette|Strydom|Armourette|2008-01-17|Mrs|Y
sqlite>
Wasn't that easy? Now If I can only get SQLite to show me the table schema. For some reason it does not want to do that.
sqlite> .schema myDB.person
sqlite>
So what worked:
  • Create Table (passed)
  • Insert (passed)
  • Auto Increment (passed)
  • Default values (passed)
  • .schema (failed)
There must be a configuration or command somewhere for schema which I must have missed.

I solved the problem by exiting SQLite and opening my database as a parameter to the SQLite command line executable.

C:\sqlite-3_5_4\sqlite3.exe myDB.fl
SQLite version 3.5.4
Enter ".help" for instructions
sqlite> .schema person
CREATE TABLE person
(
'ID' INTEGER PRIMARY KEY AUTOINCREMENT,
'FIRSTNAMES' TEXT,
'SURNAME' TEXT,
'NAME' TEXT,
'BIRTHDATE' DEFAULT CURRENT_DATE,
'TITLE' TEXT,
'ACTIVE' TEXT(1) DEFAULT 'N'
);
sqlite>
So the master database is now my SQLite database! Isn't that cool? :)

With my database as the master I am also able to get the schema by running the following SELECT statement
sqlite> SELECT * FROM sqlite_master WHERE type IN ('table') AND tbl_name NOT LIKE 'INFORMATION_SCHEMA_%' ;
table|person|person|2|CREATE TABLE person
(
'ID' INTEGER PRIMARY KEY AUTOINCREMENT,
'FIRSTNAMES' TEXT,
'SURNAME' TEXT,
'NAME' TEXT,
'BIRTHDATE' DEFAULT CURRENT_DATE,
'TITLE' TEXT,
'ACTIVE' TEXT(1) DEFAULT 'N'
)
table|sqlite_sequence|sqlite_sequence|3|CREATE TABLE sqlite_sequence(name,seq)
sqlite>
Conclusion?
  • .schema (!passed!)

Wednesday, January 9, 2008

SQLite - my journey starts

I do C++ programming in my free time and one of the challenges I am facing at the moment is databases. I have a project that require me to use a database for storing data.

Most of my development is taking place on my gaming PC with Windows XP Professional (Although I also work a bit on Fedora Linux)

My search led met to Sqlite

Sqlite is :
and has many more features...

I downloaded the Windows command-line version and started it up.
SQLite version 3.5.4
Enter ".help" for instructions
sqlite>
Figuring out how to create a database was very easy using the Sqlite language syntax as on the Sqlite website
sqlite> ATTACH DATABASE 'myDB.fl' AS myDB;
That command created a database for me and I was able to see where it is located by entering .databases at the Sqlite command prompt.

sqlite> .databases
seq name file
--- ---- ---------------------
0 main
2 myDB C:\sqlite-3_5_4\myDB.fl

So far I like Sqlite allot. It's easy to use and it just simply works.