1
0
mirror of https://github.com/VCMP-SqMod/SqMod.git synced 2024-11-08 16:57:16 +01:00
SqMod/modules/sqlite/Common.cpp
Sandu Liviu Catalin 1a312f7e7f Implemented the SQLite transaction class using the RAII pattern.
Fixed an issue that generated bad messages for errors that occurred in the formatted query execution method.
Minor adjustments throughout the code structure.
2016-03-23 05:43:19 +02:00

537 lines
18 KiB
C++

// ------------------------------------------------------------------------------------------------
#include "Common.hpp"
#include "Module.hpp"
#include "Connection.hpp"
// ------------------------------------------------------------------------------------------------
#include <ctype.h>
#include <stdarg.h>
// ------------------------------------------------------------------------------------------------
#include <sqrat.h>
// ------------------------------------------------------------------------------------------------
namespace SqMod {
// ------------------------------------------------------------------------------------------------
static SQChar g_Buffer[4096]; // Common buffer to reduce memory allocations.
// ------------------------------------------------------------------------------------------------
SStr GetTempBuff()
{
return g_Buffer;
}
// ------------------------------------------------------------------------------------------------
Uint32 GetTempBuffSize()
{
return sizeof(g_Buffer);
}
// ------------------------------------------------------------------------------------------------
void SqThrowF(CSStr str, ...)
{
// Initialize the argument list
va_list args;
va_start (args, str);
// Write the requested contents
if (snprintf(g_Buffer, sizeof(g_Buffer), str, args) < 0)
strcpy(g_Buffer, "Unknown error has occurred");
// Release the argument list
va_end(args);
// Throw the exception with the resulted message
throw Sqrat::Exception(g_Buffer);
}
// ------------------------------------------------------------------------------------------------
CSStr FmtStr(CSStr str, ...)
{
// Initialize the argument list
va_list args;
va_start (args, str);
// Write the requested contents
if (snprintf(g_Buffer, sizeof(g_Buffer), str, args) < 0)
g_Buffer[0] = 0; /* make sure the string is terminated */
// Release the argument list
va_end(args);
// Return the data from the buffer
return g_Buffer;
}
// ------------------------------------------------------------------------------------------------
CSStr QFmtStr(CSStr str, ...)
{
// Initialize the argument list
va_list args;
va_start (args, str);
// Write the requested contents
sqlite3_vsnprintf(sizeof(g_Buffer), g_Buffer, str, args);
// Release the argument list
va_end(args);
// Return the data from the buffer
return g_Buffer;
}
// ------------------------------------------------------------------------------------------------
bool IsQueryEmpty(CSStr str)
{
// Is the pointer valid?
if (!str)
return true;
// Currently processed character
SQChar c = 0;
// See if the query contains any alpha numeric characters
while ((c = *str) != 0)
{
if (isalnum(c) != 0)
return false;
++str;
}
// At this point we consider the query empty
return true;
}
// ------------------------------------------------------------------------------------------------
StackGuard::StackGuard(HSQUIRRELVM vm)
: m_Top(sq_gettop(vm)), m_VM(vm)
{
/* ... */
}
// ------------------------------------------------------------------------------------------------
StackGuard::~StackGuard()
{
sq_pop(m_VM, sq_gettop(m_VM) - m_Top);
}
// ------------------------------------------------------------------------------------------------
ConnHnd::Handle::~Handle()
{
// Is there anything to close?
if (!mPtr)
return; // Nothing to close
// Are we dealing with a memory leak? Technically shouldn't reach this situation!
else if (mRef != 0)
// Should we deal with undefined behavior instead? How bad is one connection left open?
_SqMod->LogErr("SQLite connection is still referenced (%s)", mName.c_str());
else
{
// NOTE: Should we call sqlite3_interrupt(...) before closing?
Object env;
Function func;
// Flush remaining queries in the queue and ignore the result
Flush(mQueue.size(), env, func);
// Attempt to close the database
if ((sqlite3_close(mPtr)) != SQLITE_OK)
_SqMod->LogErr("Unable to close SQLite connection [%s]", sqlite3_errmsg(mPtr));
}
}
// ------------------------------------------------------------------------------------------------
void ConnHnd::Handle::Create(CSStr name, Int32 flags, CSStr vfs)
{
// Make sure a previous connection doesn't exist
if (mPtr)
STHROWF("Unable to connect to database. Database already connected");
// Make sure the name is valid
else if (!name || strlen(name) <= 0)
STHROWF("Unable to connect to database. The name is invalid");
// Attempt to create the database connection
else if ((mStatus = sqlite3_open_v2(name, &mPtr, flags, vfs)) != SQLITE_OK)
{
// Must be destroyed regardless of result
sqlite3_close(mPtr);
// Explicitly make sure it's null
mPtr = NULL;
// Now its safe to throw the error
STHROWF("Unable to connect to database [%s]", sqlite3_errstr(mStatus));
}
// Let's save the specified information
mName.assign(name);
mFlags = flags;
mVFS.assign(vfs ? vfs : _SC(""));
// Optional check if database is initially stored in memory
mMemory = (mName.compare(_SC(":memory:")) == 0);
}
// ------------------------------------------------------------------------------------------------
Int32 ConnHnd::Handle::Flush(Uint32 num, Object & env, Function & func)
{
// Do we even have a valid connection?
if (!mPtr)
{
return -1; // No connection!
}
// Is there anything to flush?
else if (!num || mQueue.empty())
{
return 0; // Nothing to process!
}
// Can we even flush that many?
else if (num > mQueue.size())
{
num = mQueue.size();
}
// Generate the function that should be called upon error
Function callback = Function(env.GetVM(), env.GetObject(), func.GetFunc());
// Obtain iterators to the range of queries that should be flushed
QueryList::iterator itr = mQueue.begin();
QueryList::iterator end = mQueue.begin() + num;
// Attempt to begin the flush transaction
if ((mStatus = sqlite3_exec(mPtr, "BEGIN", NULL, NULL, NULL)) != SQLITE_OK)
{
STHROWF("Unable to begin flush transaction [%s]", sqlite3_errmsg(mPtr));
}
// Process all queries within range of selection
for (; itr != end; ++itr)
{
// Should we manually terminate this query?
/*
if (*(*itr).rbegin() != ';')
{
itr->push_back(';');
}
*/
// Attempt to execute the currently processed query string
if ((mStatus = sqlite3_exec(mPtr, itr->c_str(), NULL, NULL, NULL)) == SQLITE_OK)
{
continue;
}
// Do we have to execute any callback to resolve our issue?
else if (!callback.IsNull())
{
try
{
// Ask the callback whether the query processing should end here
SharedPtr< bool > ret = callback.Evaluate< bool, Int32, const String & >(mStatus, *itr);
// Should we break here?
if (!!ret && (*ret == false))
{
break;
}
}
catch (const Sqrat::Exception & e)
{
_SqMod->LogErr("Squirrel error caught in flush handler [%s]", e.Message().c_str());
}
catch (const std::exception & e)
{
_SqMod->LogErr("Program error caught in flush handler [%s]", e.what());
}
catch (...)
{
_SqMod->LogErr("Unknown error caught in flush handler");
}
}
}
// Erase all queries till end or till the point of failure (if any occurred)
mQueue.erase(mQueue.begin(), itr);
// Attempt to commit changes requested during transaction
if ((mStatus = sqlite3_exec(mPtr, "COMMIT", NULL, NULL, NULL)) == SQLITE_OK)
{
return sqlite3_changes(mPtr);
}
// Attempt to roll back erroneous changes
else if ((mStatus = sqlite3_exec(mPtr, "ROLLBACK", NULL, NULL, NULL)) != SQLITE_OK)
{
STHROWF("Unable to rollback flush transaction [%s]", sqlite3_errmsg(mPtr));
}
// The transaction failed somehow but we managed to rollback
else
{
STHROWF("Unable to commit flush transaction [%s]", sqlite3_errmsg(mPtr));
}
// Operation failed
return -1;
}
// ------------------------------------------------------------------------------------------------
StmtHnd::Handle::~Handle()
{
// Is there anything to finalize?
if (!mPtr)
return; // Nothing to finalize
// Are we dealing with a memory leak? Technically shouldn't reach this situation!
else if (mRef != 0)
// Should we deal with undefined behavior instead? How bad is one statement left alive?
_SqMod->LogErr("SQLite statement is still referenced (%s)", mQuery.c_str());
else
{
// Attempt to finalize the statement
if ((sqlite3_finalize(mPtr)) != SQLITE_OK)
_SqMod->LogErr("Unable to finalize SQLite statement [%s]", mConn.ErrMsg());
}
}
// ------------------------------------------------------------------------------------------------
void StmtHnd::Handle::Create(CSStr query)
{
// Make sure a previous statement doesn't exist
if (mPtr)
STHROWF("Unable to prepare statement. Statement already prepared");
// Is the specified database connection is valid?
else if (!mConn)
STHROWF("Unable to prepare statement. Invalid connection handle");
// Save the query string and therefore multiple strlen(...) calls
mQuery.assign(query ? query : _SC(""));
// Is the specified query string we just saved, valid?
if (mQuery.empty())
STHROWF("Unable to prepare statement. Invalid query string");
// Attempt to prepare a statement with the specified query string
else if ((mStatus = sqlite3_prepare_v2(mConn, mQuery.c_str(), (Int32)mQuery.size(),
&mPtr, NULL)) != SQLITE_OK)
{
// Clear the query string since it failed
mQuery.clear();
// Explicitly make sure the handle is null
mPtr = NULL;
// Now it's safe to throw the error
STHROWF("Unable to prepare statement [%s]", mConn.ErrMsg());
}
else
// Obtain the number of available columns
mColumns = sqlite3_column_count(mPtr);
}
// ------------------------------------------------------------------------------------------------
Int32 StmtHnd::Handle::GetColumnIndex(CSStr name)
{
// Validate the handle
if (!mPtr)
STHROWF("Invalid SQLite statement");
// Are the names cached?
else if (mIndexes.empty())
{
for (Int32 i = 0; i < mColumns; ++i)
{
// Get the column name at the current index
CSStr name = (CSStr)sqlite3_column_name(mPtr, i);
// Validate the name
if (!name)
STHROWF("Unable to retrieve column name for index (%d)", i);
// Save it to guarantee the same lifetime as this instance
else
mIndexes[name] = i;
}
}
// Attempt to find the specified column
const Indexes::iterator itr = mIndexes.find(name);
// Was there a column with the specified name?
if (itr != mIndexes.end())
return itr->second;
// No such column exists (expecting the invoker to validate the result)
return -1;
}
// ------------------------------------------------------------------------------------------------
Transaction::Transaction(const Connection & db)
: Transaction(db.GetHandle())
{
/* ... */
}
// ------------------------------------------------------------------------------------------------
Transaction::Transaction(const ConnHnd & db)
: m_Connection(db), m_Committed(false)
{
// Was the specified database connection valid?
if (!m_Connection)
{
STHROWF("Invalid connection handle");
}
// Attempt to begin transaction
else if ((m_Connection = sqlite3_exec(m_Connection, "BEGIN", NULL, NULL, NULL)) != SQLITE_OK)
{
STHROWF("Unable to begin transaction [%s]", m_Connection.ErrMsg());
}
}
// ------------------------------------------------------------------------------------------------
Transaction::~Transaction()
{
// Was this transaction successfully committed?
if (m_Committed)
{
return; // We're done here!
}
// Attempt to roll back changes because this failed to commit
if ((m_Connection = sqlite3_exec(m_Connection, "ROLLBACK", NULL, NULL, NULL)) != SQLITE_OK)
{
STHROWF("Unable to rollback transaction [%s]", m_Connection.ErrMsg());
}
}
// ------------------------------------------------------------------------------------------------
bool Transaction::Commit()
{
// We shouldn't even be here if there wasn't a valid connection but let's be sure
if (!m_Connection)
{
STHROWF("Invalid database connection");
}
// Was this transaction already committed?
else if (m_Committed)
{
STHROWF("Transaction was already committed");
}
// Attempt to commit the change during this transaction
else if ((m_Connection = sqlite3_exec(m_Connection, "COMMIT", NULL, NULL, NULL)) != SQLITE_OK)
{
STHROWF("Unable to commit transaction [%s]", m_Connection.ErrMsg());
}
else
{
m_Committed = true; // Everything was committed successfully
}
// Return the result
return m_Committed;
}
// ------------------------------------------------------------------------------------------------
CSStr GetErrStr(Int32 status)
{
return sqlite3_errstr(status);
}
// ------------------------------------------------------------------------------------------------
void SetSoftHeapLimit(Int32 limit)
{
sqlite3_soft_heap_limit(limit);
}
// ------------------------------------------------------------------------------------------------
Int32 ReleaseMemory(Int32 bytes)
{
return sqlite3_release_memory(bytes);
}
// ------------------------------------------------------------------------------------------------
Object GetMemoryUsage()
{
// Obtain the initial stack size
const StackGuard sg(_SqVM);
// Push a long integer instance with the requested value on the stack
_SqMod->PushSLongObject(_SqVM, sqlite3_memory_used());
// Obtain the object from the stack and return it
return Var< Object >(_SqVM, -1).value;
}
// ------------------------------------------------------------------------------------------------
Object GetMemoryHighwaterMark(bool reset)
{
// Obtain the initial stack size
const StackGuard sg(_SqVM);
// Push a long integer instance with the requested value on the stack
_SqMod->PushSLongObject(_SqVM, sqlite3_memory_highwater(reset));
// Obtain the object from the stack and return it
return Var< Object >(_SqVM, -1).value;
}
// ------------------------------------------------------------------------------------------------
CSStr EscapeString(CSStr str)
{
// Is there even a string to escape?
if (!str)
return _SC(""); // Default to empty string
// Attempt to escape the specified string
sqlite3_snprintf(sizeof(g_Buffer), g_Buffer, "%q", str);
// Return the resulted string
return g_Buffer;
}
// ------------------------------------------------------------------------------------------------
CCStr EscapeStringEx(SQChar spec, CCStr str)
{
// Utility that allows changing the format specifier temporarily
static SQChar fs[] = _SC("%q");
// Validate the specified format specifier
if (spec != 'q' && spec != 'Q' && spec != 'w' && spec != 's')
{
STHROWF("Unknown format specifier: %c", spec);
// Default to empty string
return _SC("");
}
// Is there even a string to escape?
else if (!str)
return _SC(""); // Default to empty string
// Apply the format specifier
fs[1] = spec;
// Attempt to escape the specified string
sqlite3_snprintf(sizeof(g_Buffer), g_Buffer, fs, str);
// Restore the format specifier
fs[1] = 'q';
// Return the resulted string
return g_Buffer;
}
// ------------------------------------------------------------------------------------------------
CCStr ArrayToQueryColumns(Array & arr)
{
// Do we even have any elements to process?
if (arr.Length() <= 0)
return _SC(""); // Default to empty string
// Allocate a vector with the required amount of column names
std::vector< String > values(arr.Length());
// Attempt to extract the array elements as strings
arr.GetArray< String >(&values[0], values.size());
// Used to know the position of the next column name
Uint32 offset = 0;
// Obtain the start of the array
std::vector< String >::iterator itr = values.begin();
// Process all elements within range
for (; itr != values.end() && offset < sizeof(g_Buffer); ++itr)
{
// Is the name valid?
if (itr->empty())
STHROWF("Invalid column name");
// Attempt to append the column name to the buffer
sqlite3_snprintf(sizeof(g_Buffer) - offset, g_Buffer + offset, "[%q], ", itr->c_str());
// Add the column name size to the offset
offset += itr->size();
// Also include the comma and space in the offset
offset += 2;
}
// Trim the last coma and space
if (offset >= 2)
g_Buffer[offset-2] = 0;
else
g_Buffer[0] = 0;
// Return the resulted string
return g_Buffer;
}
// ------------------------------------------------------------------------------------------------
CCStr TableToQueryColumns(Table & tbl)
{
// Used to know the position of the next column name
Uint32 offset = 0;
// Used to obtain the column name temporarily
String name;
// Obtain the start of the table
Table::iterator itr;
// Process all elements within range
while (tbl.Next(itr))
{
// Use the element key as the column name
name.assign(itr.getName());
// Is the name valid?
if (name.empty())
STHROWF("Invalid or empty column name");
// Attempt to append the column name to the buffer
sqlite3_snprintf(sizeof(g_Buffer) - offset, g_Buffer + offset, "[%q], ", name.c_str());
// Add the column name size to the offset
offset += name.size();
// Also include the comma and space in the offset
offset += 2;
}
// Trim the last coma and space
if (offset >= 2)
g_Buffer[offset-2] = 0;
else
g_Buffer[0] = 0;
// Return the resulted string
return g_Buffer;
}
} // Namespace:: SqMod