New SQLite Integration Tutorial (All Platforms)
by Dreamer · 08/17/2006 (12:16 pm) · 28 comments
Download Code File
Well I learned alot in the year or so since I adapted the original SQLite Tutorial for Linux.
One thing I hated about that resource was the compile process would stop with an error then you had to manually copy the lib to the right directory.
The other part I didn't like was it required using a different lib for windows and linux.
This tutorial aims to resolve that.
Just a heads up to MMORPG Enhancement Kit owners, this is already in the Kit SVN and will be part of the next patch as well.
Ok now on to the tutorial :)
First thing you want to do is go get SQLite.
You want this one
Next extract it.
Rename the top directory SQLite3
Move that directory to your engine directory.
Next open targets.torque.mk and add the following
Now just adjust the make targets
Ok so now the code is embedded directly in the engine if your running linux, if your running windows the process is exactly the same, except after editing the make files (always edit make files as you work that way porting to Mac and Linux is MUCH easier), you will need to create a new project and just add all the sources for SQLite to that project, then add the project to the dependencies for TGE.
The next step would be actually utilizing SQLite from with TGE.
For that I have included 2 files I did not write but I did update and adapt to work with SQLite3.
Those are SQLiteObject.cc and SQLiteObject.h just drop them in your engine/console directory.
Finally add the SQLiteObject.cc file to the console make target
This is the SQLiteObject.cc file updated for SQLite3...
Well I hope you find this helpful,
Regards,
Dreamer
p.s. The original SQLite Integration Tutorial was written by John Vanderbeck as is available here SQLite Integration for Torque It has all the details on how to use a SQLiteObject in your program. Special thanks to John for this.
Well I learned alot in the year or so since I adapted the original SQLite Tutorial for Linux.
One thing I hated about that resource was the compile process would stop with an error then you had to manually copy the lib to the right directory.
The other part I didn't like was it required using a different lib for windows and linux.
This tutorial aims to resolve that.
Just a heads up to MMORPG Enhancement Kit owners, this is already in the Kit SVN and will be part of the next patch as well.
Ok now on to the tutorial :)
First thing you want to do is go get SQLite.
You want this one
Next extract it.
Rename the top directory SQLite3
Move that directory to your engine directory.
Next open targets.torque.mk and add the following
SOURCE.SQLITE=\ sqlite3/alter.c\ sqlite3/delete.c\ sqlite3/os.c\ sqlite3/random.c\ sqlite3/vacuum.c\ sqlite3/analyze.c\ sqlite3/expr.c\ sqlite3/os_os2.c\ sqlite3/select.c\ sqlite3/vdbe.c\ sqlite3/attach.c\ sqlite3/func.c\ sqlite3/os_unix.c\ sqlite3/vdbeapi.c\ sqlite3/auth.c\ sqlite3/hash.c\ sqlite3/os_win.c\ sqlite3/table.c\ sqlite3/vdbeaux.c\ sqlite3/btree.c\ sqlite3/insert.c\ sqlite3/pager.c\ sqlite3/tokenize.c\ sqlite3/vdbefifo.c\ sqlite3/build.c\ sqlite3/legacy.c\ sqlite3/parse.c\ sqlite3/trigger.c\ sqlite3/vdbemem.c\ sqlite3/callback.c\ sqlite3/loadext.c\ sqlite3/pragma.c\ sqlite3/update.c\ sqlite3/vtab.c\ sqlite3/complete.c\ sqlite3/main.c\ sqlite3/prepare.c\ sqlite3/utf.c\ sqlite3/where.c\ sqlite3/date.c\ sqlite3/opcodes.c\ sqlite3/printf.c\ sqlite3/util.c
Now just adjust the make targets
SOURCE.ENGINE =\
$(SOURCE.COLLISION) \
$(SOURCE.CONSOLE) \
$(SOURCE.CORE) \
$(SOURCE.DGL) \
$(SOURCE.I18N) \
$(SOURCE.INTERIOR) \
$(SOURCE.MATH) \
$(SOURCE.PLATFORM) \
$(SOURCE.SCENEGRAPH) \
$(SOURCE.SIM) \
$(SOURCE.TERRAIN) \
$(SOURCE.TS) \
$(SOURCE.AUDIO) \
$(SOURCE.GUI) \
$(SOURCE.GAME) \
$(SOURCE.GAME.FPS) \
$(SOURCE.GAME.NET) \
$(SOURCE.GAME.FX) \
$(SOURCE.GAME.VEHICLES) \
$(SOURCE.UTIL) \
$(SOURCE.SQLITE)
SOURCE.TESTAPP =\
$(SOURCE.AUDIO) \
$(SOURCE.COLLISION) \
$(SOURCE.CONSOLE) \
$(SOURCE.CORE) \
$(SOURCE.DGL) \
$(SOURCE.EDITOR) \
$(SOURCE.GUI) \
$(SOURCE.GAME) \
$(SOURCE.GAME.FPS) \
$(SOURCE.GAME.NET) \
$(SOURCE.GAME.FX) \
$(SOURCE.GAME.VEHICLES) \
$(SOURCE.UTIL) \
$(SOURCE.I18N) \
$(SOURCE.INTERIOR) \
$(SOURCE.MATH) \
$(SOURCE.PLATFORM) \
$(SOURCE.SCENEGRAPH) \
$(SOURCE.SIM) \
$(SOURCE.TERRAIN) \
$(SOURCE.TS) \
$(SOURCE.SQLITE)Ok so now the code is embedded directly in the engine if your running linux, if your running windows the process is exactly the same, except after editing the make files (always edit make files as you work that way porting to Mac and Linux is MUCH easier), you will need to create a new project and just add all the sources for SQLite to that project, then add the project to the dependencies for TGE.
The next step would be actually utilizing SQLite from with TGE.
For that I have included 2 files I did not write but I did update and adapt to work with SQLite3.
Those are SQLiteObject.cc and SQLiteObject.h just drop them in your engine/console directory.
Finally add the SQLiteObject.cc file to the console make target
SOURCE.CONSOLE=\ console/astAlloc.cc \ console/astNodes.cc \ console/BASscan.cc \ console/BASgram.cc \ console/codeBlock.cc \ console/compiledEval.cc \ console/compiler.cc \ console/console.cc \ console/consoleDoc.cc \ console/consoleFunctions.cc \ console/consoleInternal.cc \ console/consoleLogger.cc \ console/consoleObject.cc \ console/consoleParser.cc \ console/consoleTypes.cc \ console/cmdgram.cc \ console/CMDscan.cc \ console/dynamicTypes.cc \ console/scriptObject.cc \ console/simBase.cc \ console/simDictionary.cc \ console/simManager.cc \ console/stringStack.cc \ console/telnetConsole.cc \ console/telnetDebugger.cc \ console/typeValidators.cc \ console/SQLiteObject.cc
This is the SQLiteObject.cc file updated for SQLite3...
//-----------------------------------------------------------------------------
// Torque Game Engine
// Written by John Vanderbeck
//
// This code is written by John Vanderbeck and is offered freely to the Torque
// Game Engine wth no express warranties. Use it for whatever you want, all
// I ask is that you don't rip it off and call it your own. Credit where
// credit is due. If you do use this, just drop me a line to let me know. It
// makes me feel good :)
// Contact: jvanderbeck@novusdelta.com
// http://www.novusdelta.com
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// This code implements support for SQLite into Torque and TorqueScript
//
// Essentially this creates a scriptable object that interfaces with SQLite.
//-----------------------------------------------------------------------------
#include "console/SQLiteObject.h"
#include "console/simBase.h"
#include "console/consoleInternal.h"
#include <stdlib.h>
IMPLEMENT_CONOBJECT(SQLiteObject);
SQLiteObject::SQLiteObject()
{
m_pDatabase = NULL;
m_szErrorString = NULL;
m_iLastResultSet = 0;
m_iNextResultSet = 1;
}
SQLiteObject::~SQLiteObject()
{
S32 index;
// if we still have a database open, close it
CloseDatabase();
// Clear out any error string we may have left
ClearErrorString();
// Clean up result sets
//
// Boy oh boy this is such a crazy hack!
// I can't seem to iterate through a vector and clean it up without screwing the vector.
// So (HACK HACK HACK) what i'm doing for now is making a temporary vector that
// contains a list of the result sets that the user hasn't cleaned up.
// Clean up all those result sets, then delete the temp vector.
Vector<int> vTemp;
Vector<int>::iterator iTemp;
VectorPtr<sqlite_resultset*>::iterator i;
for (i = m_vResultSets.begin(); i != m_vResultSets.end(); i++)
{
vTemp.push_back((*i)->iResultSet);
}
index = 0;
for (iTemp = vTemp.begin(); iTemp != vTemp.end(); iTemp++)
{
Con::warnf("SQLiteObject Warning: Result set #%i was not cleared by script. Clearing it now.", vTemp[index]);
ClearResultSet(vTemp[index]);
index++;
}
m_vResultSets.clear();
}
bool SQLiteObject::processArguments(S32 argc, const char **argv)
{
if(argc == 0)
return true;
else
return true;
return false;
}
bool SQLiteObject::onAdd()
{
if (!Parent::onAdd())
return false;
const char *name = getName();
if(name && name[0] && getClassRep())
{
Namespace *parent = getClassRep()->getNameSpace();
Con::linkNamespaces(parent->mName, name);
mNameSpace = Con::lookupNamespace(name);
}
return true;
}
// This is the function that gets called when an instance
// of your object is being removed from the system and being
// destroyed. Use this to do your clean up and what not.
void SQLiteObject::onRemove()
{
CloseDatabase();
Parent::onRemove();
}
// To be honest i'm not 100% sure on when this is called yet.
// Basically its used to set the values of any persistant fields
// the object has. Similiar to the way datablocks work. I'm
// just not sure how and when this gets called.
void SQLiteObject::initPersistFields()
{
Parent::initPersistFields();
}
//-----------------------------------------------------------------------
// These functions below are our custom functions that we will tie into
// script.
int Callback(void *pArg, int argc, char **argv, char **columnNames)
{
// basically this callback is called for each row in the SQL query result.
// for each row, argc indicates how many columns are returned.
// columnNames[i] is the name of the column
// argv[i] is the value of the column
sqlite_resultrow* pRow;
sqlite_resultset* pResultSet;
char* name;
char* value;
int i;
if (argc == 0)
return 0;
pResultSet = (sqlite_resultset*)pArg;
if (!pResultSet)
return -1;
// create a new result row
pRow = new sqlite_resultrow;
pResultSet->iNumCols = argc;
// loop through all the columns and stuff them into our row
for (i = 0; i < argc; i++)
{
// DBEUG CODE
// Con::printf("%s = %s\n", columnNames[i], argv[i] ? argv[i] : "NULL");
name = new char[dStrlen(columnNames[i]) + 1];
dStrcpy(name, columnNames[i]);
pRow->vColumnNames.push_back(name);
if (argv[i])
{
value = new char[dStrlen(argv[i]) + 1];
dStrcpy(value, argv[i]);
pRow->vColumnValues.push_back(value);
}
else
{
value = new char[10];
dStrcpy(value, "NULL");
pRow->vColumnValues.push_back(value);
}
}
pResultSet->iNumRows++;
pResultSet->vRows.push_back(pRow);
// return 0 or else the sqlexec will be aborted.
return 0;
}
bool SQLiteObject::OpenDatabase(const char* filename)
{
// check to see if we already have an open database, and
// if so, close it.
CloseDatabase();
// We persist the error string so that the script may make a
// GetLastError() call at any time. However when we get
// ready to make a call which could result in a new error,
// we need to clear what we have to avoid a memory leak.
ClearErrorString();
//m_pDatabase = sqlite3_open(filename, 0, &m_szErrorString);
int sqlite_err = sqlite3_open(filename,&m_pDatabase);
if (sqlite_err != 0){
m_szErrorString = (char *) sqlite3_errmsg(m_pDatabase);
m_pDatabase=NULL;
}
if (m_pDatabase == 0)
{
// there was an error and the database could not
// be opened.
Con::executef(this, 2, "onOpenFailed()", m_szErrorString);
return false;
}
else
{
// database was opened without error
Con::executef(this, 1, "onOpened()");
}
return true;
}
int SQLiteObject::ExecuteSQL(const char* sql)
{
int iResult;
sqlite_resultset* pResultSet;
// create a new resultset
pResultSet = new sqlite_resultset;
if (pResultSet)
{
pResultSet->bValid = false;
pResultSet->iCurrentColumn = 0;
pResultSet->iCurrentRow = 0;
pResultSet->iNumCols = 0;
pResultSet->iNumRows = 0;
pResultSet->iResultSet = m_iNextResultSet;
pResultSet->vRows.clear();
m_iLastResultSet = m_iNextResultSet;
m_iNextResultSet++;
}
else
return 0;
iResult = sqlite3_exec(m_pDatabase, sql, Callback, (void*)pResultSet, &m_szErrorString);
if (iResult == 0)
{
//SQLITE_OK
SaveResultSet(pResultSet);
Con::executef(this, 1, "onQueryFinished()");
return pResultSet->iResultSet;
}
else
{
// error occured
Con::executef(this, 2, "onQueryFailed", m_szErrorString);
delete pResultSet;
return 0;
}
return 0;
}
void SQLiteObject::CloseDatabase()
{
if (m_pDatabase)
sqlite3_close(m_pDatabase);
m_pDatabase = NULL;
}
void SQLiteObject::NextRow(int resultSet)
{
sqlite_resultset* pResultSet;
pResultSet = GetResultSet(resultSet);
if (!pResultSet)
return;
pResultSet->iCurrentRow++;
}
bool SQLiteObject::EndOfResult(int resultSet)
{
sqlite_resultset* pResultSet;
pResultSet = GetResultSet(resultSet);
if (!pResultSet)
return true;
if (pResultSet->iCurrentRow >= pResultSet->iNumRows)
return true;
return false;
}
void SQLiteObject::ClearErrorString()
{
if (m_szErrorString)
sqlite3_free(m_szErrorString);
m_szErrorString = NULL;
}
void SQLiteObject::ClearResultSet(int index)
{
sqlite_resultset* resultSet;
sqlite_resultrow* resultRow;
S32 rows, cols, iResultSet;
// Get the result set specified by index
resultSet = GetResultSet(index);
iResultSet = GetResultSetIndex(index);
if ((!resultSet) || (!resultSet->bValid))
{
Con::warnf("Warning SQLiteObject::ClearResultSet(%i) failed to retrieve specified result set. Result set was NOT cleared.", index);
return;
}
// Now we have the specific result set to be cleared.
// What we need to do now is iterate through each "Column" in each "Row"
// and free the strings, then delete the entries.
VectorPtr<sqlite_resultrow*>::iterator iRow;
VectorPtr<char*>::iterator iColumnName;
VectorPtr<char*>::iterator iColumnValue;
for (iRow = resultSet->vRows.begin(); iRow != resultSet->vRows.end(); iRow++)
{
// Iterate through rows
// for each row iterate through all the column values and names
for (iColumnName = (*iRow)->vColumnNames.begin(); iColumnName != (*iRow)->vColumnNames.end(); iColumnName++)
{
// Iterate through column names. Free the memory.
delete[] (*iColumnName);
}
for (iColumnValue = (*iRow)->vColumnValues.begin(); iColumnValue != (*iRow)->vColumnValues.end(); iColumnValue++)
{
// Iterate through column values. Free the memory.
delete[] (*iColumnValue);
}
// free memory used by the row
delete (*iRow);
}
// empty the resultset
resultSet->vRows.clear();
resultSet->bValid = false;
delete resultSet;
m_vResultSets.erase_fast(iResultSet);
}
sqlite_resultset* SQLiteObject::GetResultSet(int iResultSet)
{
// Get the result set specified by iResultSet
VectorPtr<sqlite_resultset*>::iterator i;
for (i = m_vResultSets.begin(); i != m_vResultSets.end(); i++)
{
if( (*i)->iResultSet == iResultSet )
//Mythic Debug
return *i;
//break;
}
return NULL;
}
int SQLiteObject::GetResultSetIndex(int iResultSet)
{
int iIndex;
// Get the result set specified by iResultSet
VectorPtr<sqlite_resultset*>::iterator i;
iIndex = 0;
for (i = m_vResultSets.begin(); i != m_vResultSets.end(); i++)
{
if ((*i)->iResultSet == iResultSet)
break;
iIndex++;
}
return iIndex;
}
bool SQLiteObject::SaveResultSet(sqlite_resultset* pResultSet)
{
// Basically just add this to our vector. It should already be filled up.
pResultSet->bValid = true;
m_vResultSets.push_back(pResultSet);
return true;
}
int SQLiteObject::GetColumnIndex(int iResult, const char* columnName)
{
int iIndex;
VectorPtr<char*>::iterator i;
sqlite_resultset* pResultSet;
sqlite_resultrow* pRow;
pResultSet = GetResultSet(iResult);
if (!pResultSet)
return 0;
pRow = pResultSet->vRows[0];
if (!pRow)
return 0;
iIndex = 0;
for (i = pRow->vColumnNames.begin(); i != pRow->vColumnNames.end(); i++)
{
if (dStricmp((*i), columnName) == 0)
return iIndex + 1;
iIndex++;
}
return 0;
}
//-----------------------------------------------------------------------
// These functions are the code that actually tie our object into the scripting
// language. As you can see each one of these is called by scrpit and in turn
// calls the C++ class function.
ConsoleMethod(SQLiteObject, openDatabase, bool, 3, 3, "(const char* filename) Opens the database specifed by filename. Returns true or false.")
{
return object->OpenDatabase(argv[2]);
}
ConsoleMethod(SQLiteObject, closeDatabase, void, 2, 2, "Closes the active database.")
{
object->CloseDatabase();
}
ConsoleMethod(SQLiteObject, query, S32, 4, 0, "(const char* sql, int mode) Performs an SQL query on the open database and returns an identifier to a valid result set. mode is currently unused, and is reserved for future use.")
{
S32 iCount;
S32 iIndex, iLen, iNewIndex, iArg, iArgLen, i;
char* szNew;
if (argc == 4)
return object->ExecuteSQL(argv[2]);
else if (argc > 4)
{
// Support for printf type querys, as per Ben Garney's suggestion
// Basically what this does is allow the user to insert questino marks into thier query that will
// be replaced with actual data. For example:
// "SELECT * FROM data WHERE id=? AND age<7 AND name LIKE ?"
// scan the query and count the question marks
iCount = 0;
iLen = dStrlen(argv[2]);
for (iIndex = 0; iIndex < iLen; iIndex++)
{
if (argv[2][iIndex] == '?')
iCount++;
}
// now that we know how many replacements we have, we need to make sure we
// have enough arguments to replace them all. All arguments above 4 should be our data
if (argc - 4 == iCount)
{
// ok we have the correct number of arguments
// so now we need to calc the length of the new query string. This is easily achieved.
// We simply take our base string length, subtract the question marks, then add in
// the number of total characters used by our arguments.
iLen = dStrlen(argv[2]) - iCount;
for (iIndex = 1; iIndex <= iCount; iIndex++)
{
iLen = iLen + dStrlen(argv[iIndex+3]);
}
// iLen should now be the length of our new string
szNew = new char[iLen];
// now we need to replace all the question marks with the actual arguments
iLen = dStrlen(argv[2]);
iNewIndex = 0;
iArg = 1;
for (iIndex = 0; iIndex <= iLen; iIndex++)
{
if (argv[2][iIndex] == '?')
{
// ok we need to replace this question mark with the actual argument
// and iterate our pointers and everything as needed. This is no doubt
// not the best way to do this, but it works for me for now.
// My god this is really a mess.
iArgLen = dStrlen(argv[iArg + 3]);
// copy first character
szNew[iNewIndex] = argv[iArg + 3][0];
// copy rest of characters, and increment iNewIndex
for (i = 1; i < iArgLen; i++)
{
iNewIndex++;
szNew[iNewIndex] = argv[iArg + 3][i];
}
iArg++;
}
else
szNew[iNewIndex] = argv[2][iIndex];
iNewIndex++;
}
}
else
return 0; // incorrect number of question marks vs arguments
Con::printf("Old SQL: %s\nNew SQL: %s", argv[2], szNew);
return object->ExecuteSQL(szNew);
}
return 0;
}
ConsoleMethod(SQLiteObject, clearResult, void, 3, 3, "(int resultSet) Clears memory used by the specified result set, and deletes the result set.")
{
object->ClearResultSet(dAtoi(argv[2]));
}
ConsoleMethod(SQLiteObject, nextRow, void, 3, 3, "(int resultSet) Moves the result set's row pointer to the next row.")
{
sqlite_resultset* pResultSet;
pResultSet = object->GetResultSet(dAtoi(argv[2]));
if (pResultSet)
{
pResultSet->iCurrentRow++;
}
}
ConsoleMethod(SQLiteObject, previousRow, void, 3, 3, "(int resultSet) Moves the result set's row pointer to the previous row")
{
sqlite_resultset* pResultSet;
pResultSet = object->GetResultSet(dAtoi(argv[2]));
if (pResultSet)
{
pResultSet->iCurrentRow--;
}
}
ConsoleMethod(SQLiteObject, firstRow, void, 3, 3, "(int resultSet) Moves the result set's row pointer to the very first row in the result set.")
{
sqlite_resultset* pResultSet;
pResultSet = object->GetResultSet(dAtoi(argv[2]));
if (pResultSet)
{
pResultSet->iCurrentRow = 0;
}
}
ConsoleMethod(SQLiteObject, lastRow, void, 3, 3, "(int resultSet) Moves the result set's row pointer to the very last row in the result set.")
{
sqlite_resultset* pResultSet;
pResultSet = object->GetResultSet(dAtoi(argv[2]));
if (pResultSet)
{
pResultSet->iCurrentRow = pResultSet->iNumRows - 1;
}
}
ConsoleMethod(SQLiteObject, setRow, void, 4, 4, "(int resultSet int row) Moves the result set's row pointer to the row specified. Row indices start at 1 not 0.")
{
sqlite_resultset* pResultSet;
pResultSet = object->GetResultSet(dAtoi(argv[2]));
if (pResultSet)
{
pResultSet->iCurrentRow = dAtoi(argv[3]) - 1;
}
}
ConsoleMethod(SQLiteObject, getRow, S32, 3, 3, "(int resultSet) Returns what row the result set's row pointer is currently on.")
{
sqlite_resultset* pResultSet;
pResultSet = object->GetResultSet(dAtoi(argv[2]));
if (pResultSet)
{
return pResultSet->iCurrentRow + 1;
}
else
return 0;
}
ConsoleMethod(SQLiteObject, numRows, S32, 3, 3, "(int resultSet) Returns the number of rows in the result set.")
{
sqlite_resultset* pResultSet;
pResultSet = object->GetResultSet(dAtoi(argv[2]));
if (pResultSet)
{
return pResultSet->iNumRows;
}
else
return 0;
}
ConsoleMethod(SQLiteObject, numColumns, S32, 3, 3, "(int resultSet) Returns the number of columns in the result set.")
{
sqlite_resultset* pResultSet;
pResultSet = object->GetResultSet(dAtoi(argv[2]));
if (pResultSet)
{
return pResultSet->iNumCols;
}
else
return 0;
}
ConsoleMethod(SQLiteObject, endOfResult, bool, 3, 3, "(int resultSet) Checks to see if the internal pointer for the specified result set is at the end, indicating there are no more rows left to read.")
{
return object->EndOfResult(dAtoi(argv[2]));
}
ConsoleMethod(SQLiteObject, EOR, bool, 3, 3, "(int resultSet) Same as endOfResult().")
{
return object->EndOfResult(dAtoi(argv[2]));
}
ConsoleMethod(SQLiteObject, EOF, bool, 3, 3, "(int resultSet) Same as endOfResult().")
{
return object->EndOfResult(dAtoi(argv[2]));
}
ConsoleMethod(SQLiteObject, getColumnIndex, S32, 4, 4, "(resultSet columnName) Looks up the specified column name in the specified result set, and returns the columns index number. A return value of 0 indicates the lookup failed for some reason (usually this indicates you specified a column name that doesn't exist or is spelled wrong).")
{
return object->GetColumnIndex(dAtoi(argv[2]), argv[3]);
}
ConsoleMethod(SQLiteObject, getColumnName, const char *, 4, 4, "(resultSet columnIndex) Looks up the specified column index in the specified result set, and returns the column's name. A return value of an empty string indicates the lookup failed for some reason (usually this indicates you specified a column index that is invalid or exceeds the number of columns in the result set). Columns are index starting with 1 not 0")
{
sqlite_resultset* pResultSet;
sqlite_resultrow* pRow;
VectorPtr<char*>::iterator iName;
VectorPtr<char*>::iterator iValue;
S32 iColumn;
pResultSet = object->GetResultSet(dAtoi(argv[2]));
if (pResultSet)
{
pRow = pResultSet->vRows[pResultSet->iCurrentRow];
if (!pRow)
return "";
// We assume they specified column by index. If they know the column name they wouldn't be calling this function :)
iColumn = dAtoi(argv[3]);
if (iColumn == 0)
return ""; // column indices start at 1, not 0
// now we should have an index for our column name
if (pRow->vColumnNames[iColumn])
return pRow->vColumnNames[iColumn];
else
return "";
}
else
return "";
}
ConsoleMethod(SQLiteObject, getColumn, const char *, 4, 4, "(resultSet column) Returns the value of the specified column (Column can be specified by name or index) in the current row of the specified result set. If the call fails, the returned string will indicate the error.")
{
sqlite_resultset* pResultSet;
sqlite_resultrow* pRow;
VectorPtr<char*>::iterator iName;
VectorPtr<char*>::iterator iValue;
S32 iColumn;
pResultSet = object->GetResultSet(dAtoi(argv[2]));
if (pResultSet)
{
pRow = pResultSet->vRows[pResultSet->iCurrentRow];
if (!pRow)
return "invalid_row";
// Is column specified by a name or an index?
iColumn = dAtoi(argv[3]);
if (iColumn == 0)
{
// column was specified by a name
iColumn = object->GetColumnIndex(dAtoi(argv[2]), argv[3]);
// if this is still 0 then we have some error
if (iColumn == 0)
return "invalid_column";
}
// We temporarily padded the index in GetColumnIndex() so we could return a
// 0 for error. So now we need to drop it back down.
iColumn--;
// now we should have an index for our column data
if (pRow->vColumnValues[iColumn])
return pRow->vColumnValues[iColumn];
else
return "NULL";
}
else
return "invalid_result_set";
}
ConsoleMethod(SQLiteObject, escapeString, const char *, 3, 3, "(string) Escapes the given string, making it safer to pass into a query.")
{
// essentially what we need to do here is scan the string for any occurances of: ', ", and \
// and prepend them with a slash: \', \", \
// to do this we first need to know how many characters we are replacing so we can calculate
// the size of the new string
S32 iCount;
S32 iIndex, iLen, iNewIndex;
char* szNew;
iCount = 0;
iLen = dStrlen(argv[2]);
for (iIndex = 0; iIndex < iLen; iIndex++)
{
if (argv[2][iIndex] == '\'')
iCount++;
else if (argv[2][iIndex] == '\"')
iCount++;
else if (argv[2][iIndex] == '\')
iCount++;
}
// Con::printf("escapeString counts %i instances of characters to be escaped. New string will be %i characters longer for a total of %i characters.", iCount, iCount, iLen+iCount);
szNew = new char[iLen+iCount];
iNewIndex = 0;
for (iIndex = 0; iIndex <= iLen; iIndex++)
{
if (argv[2][iIndex] == '\'')
{
szNew[iNewIndex] = '\';
iNewIndex++;
szNew[iNewIndex] = '\'';
}
else if (argv[2][iIndex] == '\"')
{
szNew[iNewIndex] = '\';
iNewIndex++;
szNew[iNewIndex] = '\"';
}
else if (argv[2][iIndex] == '\')
{
szNew[iNewIndex] = '\';
iNewIndex++;
szNew[iNewIndex] = '\';
}
else
szNew[iNewIndex] = argv[2][iIndex];
iNewIndex++;
}
// Con::printf("Last characters of each string (new, old): %s, %s", argv[2][iIndex-1], szNew[iNewIndex-1]);
// Con::printf("Old String: %s\nNew String: %s", argv[2], szNew);
return szNew;
}Again if you are running windows you will want to be sure to add the SQLiteObject.cc file to your project as well.Well I hope you find this helpful,
Regards,
Dreamer
p.s. The original SQLite Integration Tutorial was written by John Vanderbeck as is available here SQLite Integration for Torque It has all the details on how to use a SQLiteObject in your program. Special thanks to John for this.
#2
I just did a major update to this and also added a working Visual Studio 2005 project file.
Regards,
Dreamer
08/21/2006 (11:32 am)
Major update...I just did a major update to this and also added a working Visual Studio 2005 project file.
Regards,
Dreamer
#3
08/22/2006 (9:52 pm)
Thanks for mod! This is potentially a very useful mod!
#4
maybe I am just being a moron, I have been awake and programming for a REALLY long time at this point but where is the Header file??
The Trusted One
08/24/2006 (3:32 am)
Okay, this is a REALLY great mod and would be of extreme use to me,maybe I am just being a moron, I have been awake and programming for a REALLY long time at this point but where is the Header file??
The Trusted One
#5
If I create a table, close the app, delete the table creating code and add table loading code, it cant find the table.
Do I have to re-create the table every time I load the app?
(Meaning I cant save stuff to the DB)
08/31/2006 (8:18 am)
I must be doing something wrong, or missing the point of this but:If I create a table, close the app, delete the table creating code and add table loading code, it cant find the table.
Do I have to re-create the table every time I load the app?
(Meaning I cant save stuff to the DB)
#6
Thanks for the resource =)
09/16/2006 (12:28 pm)
Are you supposed to use John Vanderbeck's SQLiteObject.h? I don't see one here either.Thanks for the resource =)
#7
11/16/2006 (10:08 am)
Does anyone know into which directory SQLite saves its databases? And how I can change that directory? I am getting the exception that the Torque script file throws if it cannot create a database with the given name. The message tells me to check to make sure that the drive is not write protected.
#8
11/16/2006 (10:56 am)
In John Vanderbeck's resource http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=5531 (which I believe this one was built from) the .db files were created in the same file as your torqueDemo.exe
#9
Dreamer, these files are not included?
This tutorial is a bit hard to follow, which files where to include?
The ones from John?
They are still declaring SQLITE <> V3 ?!
Sorry to bug you, but i cant get this running :(
12/14/2006 (12:37 pm)
>>>For that I have included 2 files I did not write but I did update and adapt to work with SQLite3.Dreamer, these files are not included?
This tutorial is a bit hard to follow, which files where to include?
The ones from John?
They are still declaring SQLITE <> V3 ?!
Sorry to bug you, but i cant get this running :(
#10
12/14/2006 (12:42 pm)
Richard look at the resource carefully, the SQLiteObject.cc is the last section in code blocks.
#11
For everyone else having problems:
Follow the turial from dreamer till the point where you should compile.
Remove the SQLITE.H from your (...in my case:) VC2005 project, instead insert the
SQLITE3.H
which is found in the ..../engine/sqlite3 directory.
You can drag the file from your explorer into the engine/console tree in your project.
Also make sure, you are not using John's older SQLITEOBJECT.CC, use the one Dreamer supplied here.
Its the posted codeblock (copy and paste into a new created file you name it SQLiteObject.cc)
There are downloadable files in this tutorial (except the VC 2005 project file) so you need the steps i described above.
12/14/2006 (1:00 pm)
Ok, i found the answers myself.For everyone else having problems:
Follow the turial from dreamer till the point where you should compile.
Remove the SQLITE.H from your (...in my case:) VC2005 project, instead insert the
SQLITE3.H
which is found in the ..../engine/sqlite3 directory.
You can drag the file from your explorer into the engine/console tree in your project.
Also make sure, you are not using John's older SQLITEOBJECT.CC, use the one Dreamer supplied here.
Its the posted codeblock (copy and paste into a new created file you name it SQLiteObject.cc)
There are downloadable files in this tutorial (except the VC 2005 project file) so you need the steps i described above.
#12
I can get the sqlite library to compile, but I'm a recent Windows convert and I don't know enough about XCode to get it to link with my Torque project.
(Using TGE 1.5)
01/11/2007 (2:20 pm)
Anybody have any tips for doing this in XCode on a Mac?I can get the sqlite library to compile, but I'm a recent Windows convert and I don't know enough about XCode to get it to link with my Torque project.
(Using TGE 1.5)
#13
04/23/2007 (7:18 pm)
@Jesse - I just got the sqlite3 resource rolled into my Xcode project. If you still need any help getting it to work on a Mac let me know. I may write-up a quick resource for all Xcode users.
#14
1. I downloaded the newer SQLite 3.4.0 instead of 3.3.7, just went to sqlite.org to get it.
2. I extracted sqlite to the folder /engine/sqlite
3. I copied Dreamers updated SQLiteObject.cc code and then saved it in the /engine/console directory.
4. I downloaded John Vanderbeck's resource and extracted the SQLiteObject.h file to my /engine/console directory.
It was now time to get all of this in to VC 2003. I use the Enterprise Architect version, but it should make no difference what version of VC 2003 you have.
1. I opened my TGE Project in Visual Studio 2003
2. I created a New Project and named it sqlite and created its path as /engine/sqlite (the same place all of my sqlite source files were extracted)
3. I added all .c files from my /engine/sqlite folder to my sqlite project (except tclsqlite.c, remove that file, it is to add TCL support to SQLite).
4. I went in to the configuration properties for the sqlite project. I changed GENERAL->CONFIGURATION TYPE to Static Library(.lib) then saved my settings.
5. I went in to my Torque Executable Project Dependencies and added my sqlite project as a dependency and saved my settings.
Now for the source code changes to SQLiteObject.h:
1. I changed:
I then compiled my project. NO ERRORS! Woo hoo.
You can then use John Vanderbeck's test script (sqlite.cs) to test your SQLite integration.
07/14/2007 (11:15 am)
Integrated SQLite 3.4.0 into TGE 1.5.2 compiled with VC2003. Here's how I did it. 1. I downloaded the newer SQLite 3.4.0 instead of 3.3.7, just went to sqlite.org to get it.
2. I extracted sqlite to the folder /engine/sqlite
3. I copied Dreamers updated SQLiteObject.cc code and then saved it in the /engine/console directory.
4. I downloaded John Vanderbeck's resource and extracted the SQLiteObject.h file to my /engine/console directory.
It was now time to get all of this in to VC 2003. I use the Enterprise Architect version, but it should make no difference what version of VC 2003 you have.
1. I opened my TGE Project in Visual Studio 2003
2. I created a New Project and named it sqlite and created its path as /engine/sqlite (the same place all of my sqlite source files were extracted)
3. I added all .c files from my /engine/sqlite folder to my sqlite project (except tclsqlite.c, remove that file, it is to add TCL support to SQLite).
4. I went in to the configuration properties for the sqlite project. I changed GENERAL->CONFIGURATION TYPE to Static Library(.lib) then saved my settings.
5. I went in to my Torque Executable Project Dependencies and added my sqlite project as a dependency and saved my settings.
Now for the source code changes to SQLiteObject.h:
1. I changed:
#include "console/sqlite.h"to:
#include "sqlite/sqlite3.h"2. I changed:
sqlite* m_pDatabase;to:
sqlite3* m_pDatabase;3. Saved my changes.
I then compiled my project. NO ERRORS! Woo hoo.
You can then use John Vanderbeck's test script (sqlite.cs) to test your SQLite integration.
#15
*Error 1 error LNK2019: unresolved external symbol _sqlite3_errmsg referenced in function "public: bool __thiscall SQLiteObject::OpenDatabase(char const *)" (?OpenDatabase@SQLiteObject@@QAE_NPBD@Z) SQLiteObject.obj
*Error 2 error LNK2019: unresolved external symbol _sqlite3_open referenced in function "public: bool __thiscall SQLiteObject::OpenDatabase(char const *)" (?OpenDatabase@SQLiteObject@@QAE_NPBD@Z) SQLiteObject.obj
*Error 3 error LNK2019: unresolved external symbol _sqlite3_exec referenced in function "public: int __thiscall SQLiteObject::ExecuteSQL(char const *)" (?ExecuteSQL@SQLiteObject@@QAEHPBD@Z) SQLiteObject.obj
*Error 4 error LNK2019: unresolved external symbol _sqlite3_close referenced in function "public: void __thiscall SQLiteObject::CloseDatabase(void)" (?CloseDatabase@SQLiteObject@@QAEXXZ) SQLiteObject.obj
*Error 5 error LNK2019: unresolved external symbol _sqlite3_free referenced in function "public: void __thiscall SQLiteObject::ClearErrorString(void)" (?ClearErrorString@SQLiteObject@@QAEXXZ) SQLiteObject.obj
Any one have suggestions?
12/27/2007 (9:45 am)
I can't get mine to work! I've tried every suggestion on the resource, and I get the same five errors:*Error 1 error LNK2019: unresolved external symbol _sqlite3_errmsg referenced in function "public: bool __thiscall SQLiteObject::OpenDatabase(char const *)" (?OpenDatabase@SQLiteObject@@QAE_NPBD@Z) SQLiteObject.obj
*Error 2 error LNK2019: unresolved external symbol _sqlite3_open referenced in function "public: bool __thiscall SQLiteObject::OpenDatabase(char const *)" (?OpenDatabase@SQLiteObject@@QAE_NPBD@Z) SQLiteObject.obj
*Error 3 error LNK2019: unresolved external symbol _sqlite3_exec referenced in function "public: int __thiscall SQLiteObject::ExecuteSQL(char const *)" (?ExecuteSQL@SQLiteObject@@QAEHPBD@Z) SQLiteObject.obj
*Error 4 error LNK2019: unresolved external symbol _sqlite3_close referenced in function "public: void __thiscall SQLiteObject::CloseDatabase(void)" (?CloseDatabase@SQLiteObject@@QAEXXZ) SQLiteObject.obj
*Error 5 error LNK2019: unresolved external symbol _sqlite3_free referenced in function "public: void __thiscall SQLiteObject::ClearErrorString(void)" (?ClearErrorString@SQLiteObject@@QAEXXZ) SQLiteObject.obj
Any one have suggestions?
#16
The linker error you are receiving indicates that it's not linking in the sqlite3 library for some reason.
On linux and mac this can be accomplished by making sure that -lsqlite3 is in the final build command.
In visual studio you need to check your project options and make sure that SQLite3 is the first thing to build and that TGE is set to build SQLite3 as a dependency first.
Sincerely,
Steve Morrey aka Dreamer
12/27/2007 (10:59 am)
@NathanThe linker error you are receiving indicates that it's not linking in the sqlite3 library for some reason.
On linux and mac this can be accomplished by making sure that -lsqlite3 is in the final build command.
In visual studio you need to check your project options and make sure that SQLite3 is the first thing to build and that TGE is set to build SQLite3 as a dependency first.
Sincerely,
Steve Morrey aka Dreamer
#17
Edit -> I got SQLite3 to build first by making a lot of dependancies on it, but I still get the same five errors. Also, when you say library, do you mean an actuall *.lib file? Because I don't have SQLite3.lib in my project anymore.
Edit2 -> I added SQLite3.lib to my project, I got it to build. I'm not sure I should've had to do that, but if I did, I would suggest specifically saying that in your resource.
Thanks for the awsome resource!
12/27/2007 (3:23 pm)
I have SQLite3 as a dependancy for TGE, and SQLite3 comes before TGE. Does it matter if SQLite3 is build first out of all the projects? Because I can't seem to get it to be first. I'll keep trying though.Edit -> I got SQLite3 to build first by making a lot of dependancies on it, but I still get the same five errors. Also, when you say library, do you mean an actuall *.lib file? Because I don't have SQLite3.lib in my project anymore.
Edit2 -> I added SQLite3.lib to my project, I got it to build. I'm not sure I should've had to do that, but if I did, I would suggest specifically saying that in your resource.
Thanks for the awsome resource!
#18
Also I cannot execute a query like this. SELECT * FROM login it chokes with no parameters.
01/29/2008 (12:48 pm)
Has anyone tried to save an email address into a field? I have a login table that has a field for the user's email address. I get the error tdb Error: near "@atomixgroup": syntax error and the data passed is shon@atomixgroup.com, it appears to chop off at @ character and the . for the .com part. As far as I can tell it doesn't like the @. Any ideas?Also I cannot execute a query like this. SELECT * FROM login it chokes with no parameters.
#20
01/29/2008 (12:59 pm)
Come to think of it, it would probably be best to create/use a generalized url encode and decode function, for all special strings. It will make life easier for any database queries not just SQLite 
Torque 3D Owner ArmedGeek