// ------------------------------------------------------------------------------------------------ #include "Common.hpp" #include "Module.hpp" #include "Connection.hpp" // ------------------------------------------------------------------------------------------------ #include #include #include // ------------------------------------------------------------------------------------------------ #include // ------------------------------------------------------------------------------------------------ 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 (std::vsnprintf(g_Buffer, sizeof(g_Buffer), str, args) < 0) { std::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 (std::vsnprintf(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 (std::isalnum(c) != 0) { return false; } ++str; } // At this point we consider the query empty return true; } // ------------------------------------------------------------------------------------------------ StackGuard::StackGuard() : m_VM(_SqVM), m_Top(sq_gettop(m_VM)) { /* ... */ } // ------------------------------------------------------------------------------------------------ StackGuard::StackGuard(HSQUIRRELVM vm) : m_VM(vm), m_Top(sq_gettop(vm)) { /* ... */ } // ------------------------------------------------------------------------------------------------ StackGuard::~StackGuard() { sq_pop(m_VM, sq_gettop(m_VM) - m_Top); } // -------------------------------------------------------------------------------------------- StackStrF::StackStrF(HSQUIRRELVM vm, SQInteger idx, bool fmt) : mPtr(nullptr) , mLen(-1) , mRes(SQ_OK) , mObj() , mVM(vm) { const Int32 top = sq_gettop(vm); // Reset the converted value object sq_resetobject(&mObj); // Was the string or value specified? if (top <= (idx - 1)) { mRes = sq_throwerror(vm, "Missing string or value"); } // Do we have enough values to call the format function and are we allowed to? else if (top > idx && fmt) { // Pointer to the generated string SStr str = nullptr; // Attempt to generate the specified string format mRes = sqstd_format(vm, idx, &mLen, &str); // Did the format succeeded but ended up with a null string pointer? if (SQ_SUCCEEDED(mRes) && !str) { mRes = sq_throwerror(vm, "Unable to generate the string"); } else { mPtr = const_cast< CSStr >(str); } } // Is the value on the stack an actual string? else if (sq_gettype(vm, idx) == OT_STRING) { // Obtain a reference to the string object mRes = sq_getstackobj(vm, idx, &mObj); // Could we retrieve the object from the stack? if (SQ_SUCCEEDED(mRes)) { // Keep a strong reference to the object sq_addref(vm, &mObj); // Attempt to retrieve the string value from the stack mRes = sq_getstring(vm, idx, &mPtr); } // Did the retrieval succeeded but ended up with a null string pointer? if (SQ_SUCCEEDED(mRes) && !mPtr) { mRes = sq_throwerror(vm, "Unable to retrieve the string"); } } // We have to try and convert it to string else { // Attempt to convert the value from the stack to a string mRes = sq_tostring(vm, idx); // Could we convert the specified value to string? if (SQ_SUCCEEDED(mRes)) { // Obtain a reference to the resulted object mRes = sq_getstackobj(vm, -1, &mObj); // Could we retrieve the object from the stack? if (SQ_SUCCEEDED(mRes)) { // Keep a strong reference to the object sq_addref(vm, &mObj); // Attempt to obtain the string pointer mRes = sq_getstring(vm, -1, &mPtr); } } // Pop a value from the stack regardless of the result sq_pop(vm, 1); // Did the retrieval succeeded but ended up with a null string pointer? if (SQ_SUCCEEDED(mRes) && !mPtr) { mRes = sq_throwerror(vm, "Unable to retrieve the value"); } } } // ------------------------------------------------------------------------------------------------ StackStrF::~StackStrF() { if (mVM && !sq_isnull(mObj)) { sq_release(mVM, &mObj); } } // ------------------------------------------------------------------------------------------------ void ConnHnd::Validate() const { // Is the handle valid? if ((m_Hnd == nullptr) || (m_Hnd->mPtr == nullptr)) { STHROWF("Invalid SQLite connection reference"); } } // ------------------------------------------------------------------------------------------------ void StmtHnd::Validate() const { // Is the handle valid? if ((m_Hnd == nullptr) || (m_Hnd->mPtr == nullptr)) { STHROWF("Invalid SQLite statement reference"); } } // ------------------------------------------------------------------------------------------------ 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 || *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) { // Grab the error message before destroying the handle String msg(sqlite3_errmsg(mPtr) ? sqlite3_errmsg(mPtr) : _SC("Unknown reason")); // Must be destroyed regardless of result sqlite3_close(mPtr); // Explicitly make sure it's null mPtr = nullptr; // Now its safe to throw the error STHROWF("Unable to connect to database [%s]", msg.c_str()); } // 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", nullptr, nullptr, nullptr)) != 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(), nullptr, nullptr, nullptr)) == 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", nullptr, nullptr, nullptr)) == SQLITE_OK) { return sqlite3_changes(mPtr); } // Attempt to roll back erroneous changes else if ((mStatus = sqlite3_exec(mPtr, "ROLLBACK", nullptr, nullptr, nullptr)) != 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, nullptr)) != SQLITE_OK) { // Clear the query string since it failed mQuery.clear(); // Explicitly make sure the handle is null mPtr = nullptr; // 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", nullptr, nullptr, nullptr)) != 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", nullptr, nullptr, nullptr)) != 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", nullptr, nullptr, nullptr)) != 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); } // 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