diff --git a/modules/sqlite/Common.cpp b/modules/sqlite/Common.cpp index 1fe09c20..93c6936d 100644 --- a/modules/sqlite/Common.cpp +++ b/modules/sqlite/Common.cpp @@ -1,6 +1,7 @@ // ------------------------------------------------------------------------------------------------ #include "Common.hpp" #include "Module.hpp" +#include "Connection.hpp" // ------------------------------------------------------------------------------------------------ #include @@ -322,6 +323,70 @@ Int32 StmtHnd::Handle::GetColumnIndex(CSStr name) 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) { diff --git a/modules/sqlite/Common.hpp b/modules/sqlite/Common.hpp index c156c0a2..7b317e85 100644 --- a/modules/sqlite/Common.hpp +++ b/modules/sqlite/Common.hpp @@ -212,7 +212,9 @@ private: void Grab() { if (m_Hnd) + { ++(m_Hnd->mRef); + } } /* -------------------------------------------------------------------------------------------- @@ -221,7 +223,9 @@ private: void Drop() { if (m_Hnd && --(m_Hnd->mRef) == 0) + { delete m_Hnd; // Let the destructor take care of cleaning up (if necessary) + } } /* -------------------------------------------------------------------------------------------- @@ -304,7 +308,9 @@ public: ConnHnd & operator = (Int32 status) { if (m_Hnd) + { m_Hnd->mStatus = status; + } return *this; } @@ -330,7 +336,9 @@ public: bool operator == (Int32 status) const { if (m_Hnd) + { return (m_Hnd->mStatus == status); + } return false; } @@ -340,7 +348,9 @@ public: bool operator != (Int32 status) const { if (m_Hnd) + { return (m_Hnd->mStatus != status); + } return false; } @@ -428,6 +438,14 @@ public: return m_Hnd ? m_Hnd->mRef : 0; } + /* -------------------------------------------------------------------------------------------- + * Retrieve the last known status code. + */ + Int32 Status() const + { + return m_Hnd ? m_Hnd->mStatus : SQLITE_NOMEM; + } + /* -------------------------------------------------------------------------------------------- * Retrieve the message of the last received error code. */ @@ -435,7 +453,9 @@ public: { // SQLite does it's null pointer validations internally if (m_Hnd) + { return sqlite3_errstr(sqlite3_errcode(m_Hnd->mPtr)); + } return _SC(""); } @@ -446,7 +466,9 @@ public: { // SQLite does it's null pointer validations internally if (m_Hnd) + { return sqlite3_errmsg(m_Hnd->mPtr); + } return _SC(""); } @@ -457,7 +479,9 @@ public: { // SQLite does it's null pointer validations internally if (m_Hnd) + { return sqlite3_errcode(m_Hnd->mPtr); + } return SQLITE_NOMEM; } @@ -468,7 +492,9 @@ public: { // SQLite does it's null pointer validations internally if (m_Hnd) + { return sqlite3_extended_errcode(m_Hnd->mPtr); + } return SQLITE_NOMEM; } }; @@ -581,7 +607,9 @@ private: void Grab() { if (m_Hnd) + { ++(m_Hnd->mRef); + } } /* -------------------------------------------------------------------------------------------- @@ -590,7 +618,9 @@ private: void Drop() { if (m_Hnd && --(m_Hnd->mRef) == 0) + { delete m_Hnd; // Let the destructor take care of cleaning up (if necessary) + } } /* -------------------------------------------------------------------------------------------- @@ -674,7 +704,9 @@ public: StmtHnd & operator = (Int32 status) { if (m_Hnd) + { m_Hnd->mStatus = status; + } return *this; } @@ -700,7 +732,9 @@ public: bool operator == (Int32 status) const { if (m_Hnd) + { return (m_Hnd->mStatus == status); + } return false; } @@ -710,7 +744,9 @@ public: bool operator != (Int32 status) const { if (m_Hnd) + { return (m_Hnd->mStatus != status); + } return false; } @@ -798,13 +834,23 @@ public: return m_Hnd ? m_Hnd->mRef : 0; } + /* -------------------------------------------------------------------------------------------- + * Retrieve the last known status code. + */ + Int32 Status() const + { + return m_Hnd ? m_Hnd->mStatus : SQLITE_NOMEM; + } + /* -------------------------------------------------------------------------------------------- * Retrieve the message of the last received error code. */ CCStr ErrStr() const { if (m_Hnd) + { return m_Hnd->mConn.ErrStr(); + } return _SC(""); } @@ -814,7 +860,9 @@ public: CCStr ErrMsg() const { if (m_Hnd) + { return m_Hnd->mConn.ErrMsg(); + } return _SC(""); } @@ -824,7 +872,9 @@ public: Int32 ErrNo() const { if (m_Hnd) + { return m_Hnd->mConn.ErrNo(); + } return SQLITE_NOMEM; } @@ -834,11 +884,75 @@ public: Int32 ExErrNo() const { if (m_Hnd) + { return m_Hnd->mConn.ExErrNo(); + } return SQLITE_NOMEM; } }; +/* ------------------------------------------------------------------------------------------------ + * Implements the RAII pattern for database transactions. +*/ +class Transaction +{ +public: + + /* -------------------------------------------------------------------------------------------- + * Construct by taking the handle from a connection. + */ + Transaction(const Connection & db); + + /* -------------------------------------------------------------------------------------------- + * Construct using the direct connection handle. + */ + Transaction(const ConnHnd & db); + + /* -------------------------------------------------------------------------------------------- + * Copy constructor. (disabled) + */ + Transaction(const Transaction & o) = delete; + + /* -------------------------------------------------------------------------------------------- + * Move constructor. (disabled) + */ + Transaction(Transaction && o) = delete; + + /* -------------------------------------------------------------------------------------------- + * Destructor. + */ + ~Transaction(); + + /* -------------------------------------------------------------------------------------------- + * Copy assignment operator. (disabled) + */ + Transaction & operator = (const Transaction & o) = delete; + + /* -------------------------------------------------------------------------------------------- + * Move assignment operator. (disabled) + */ + Transaction & operator = (Transaction && o) = delete; + + /* -------------------------------------------------------------------------------------------- + * Attempt to commit changes to the database. + */ + bool Commit(); + + /* -------------------------------------------------------------------------------------------- + * See whether the change during this transaction were successfully committed. + */ + bool Commited() const + { + return m_Committed; + } + +private: + + // -------------------------------------------------------------------------------------------- + ConnHnd m_Connection; // The database connection handle where the transaction began. + bool m_Committed; // Whether changes were successfully committed to the database. +}; + /* ------------------------------------------------------------------------------------------------ * Retrieve the string representation of a certain status code. */ diff --git a/modules/sqlite/Connection.cpp b/modules/sqlite/Connection.cpp index dee410a4..a68979ef 100644 --- a/modules/sqlite/Connection.cpp +++ b/modules/sqlite/Connection.cpp @@ -386,7 +386,14 @@ SQInteger Connection::ExecF(HSQUIRRELVM vm) // Attempt to execute the specified query else if ((conn->m_Handle = sqlite3_exec(conn->m_Handle, sql, NULL, NULL, NULL)) != SQLITE_OK) { - return sq_throwerror(vm, FmtStr("Unable to execute query [%s]", conn->m_Handle.ErrMsg())); + // Generate the query message first + String msg("Unable to execute query "); + // (we can't use FmtStr here because Squirrel doesn't make a copy of the message) + msg.push_back('['); + msg.append(conn->m_Handle.ErrMsg()); + msg.push_back(']'); + // Now throw the message + return sq_throwerror(vm, msg.c_str()); } } else @@ -401,7 +408,14 @@ SQInteger Connection::ExecF(HSQUIRRELVM vm) // Attempt to execute the specified query else if ((conn->m_Handle = sqlite3_exec(conn->m_Handle, sql.value, NULL, NULL, NULL)) != SQLITE_OK) { - return sq_throwerror(vm, FmtStr("Unable to execute query [%s]", conn->m_Handle.ErrMsg())); + // Generate the query message first + String msg("Unable to execute query "); + // (we can't use FmtStr here because Squirrel doesn't make a copy of the message) + msg.push_back('['); + msg.append(conn->m_Handle.ErrMsg()); + msg.push_back(']'); + // Now throw the message + return sq_throwerror(vm, msg.c_str()); } } // Push the number of changes onto the stack diff --git a/modules/sqlite/Module.cpp b/modules/sqlite/Module.cpp index 262d269f..96669da1 100644 --- a/modules/sqlite/Module.cpp +++ b/modules/sqlite/Module.cpp @@ -170,16 +170,16 @@ void RegisterAPI(HSQUIRRELVM vm) Table sqlns(vm); sqlns.Bind(_SC("Connection"), Class< Connection >(vm, _SC("SqSQLiteConnection")) - /* Constructors */ + // Constructors .Ctor() .Ctor< CCStr >() .Ctor< CCStr, Int32 >() .Ctor< CCStr, Int32, CCStr >() - /* Metamethods */ + // Metamethods .Func(_SC("_cmp"), &Connection::Cmp) .SquirrelFunc(_SC("_typename"), &Connection::Typename) .Func(_SC("_tostring"), &Connection::ToString) - /* Properties */ + // Properties .Prop(_SC("Valid"), &Connection::IsValid) .Prop(_SC("Refs"), &Connection::GetRefCount) .Prop(_SC("Connected"), &Connection::IsValid) @@ -201,7 +201,7 @@ void RegisterAPI(HSQUIRRELVM vm) .Prop(_SC("Profile"), &Connection::GetProfiling, &Connection::SetProfiling) .Prop(_SC("BusyTimeout"), (Int32 (Connection::*)(void) const)(NULL), &Connection::SetBusyTimeout) .Prop(_SC("QueueSize"), &Connection::QueueSize) - /* Functions */ + // Member Methods .Func(_SC("Release"), &Connection::Release) .Overload< void (Connection::*)(CSStr) >(_SC("Open"), &Connection::Open) .Overload< void (Connection::*)(CSStr, Int32) >(_SC("Open"), &Connection::Open) @@ -229,15 +229,15 @@ void RegisterAPI(HSQUIRRELVM vm) ); sqlns.Bind(_SC("Statement"), Class< Statement >(vm, _SC("SqSQLiteStatement")) - /* Constructors */ + // Constructors .Ctor() .Ctor< const Connection &, CCStr >() .Ctor< const Statement & >() - /* Metamethods */ + // Metamethods .Func(_SC("_cmp"), &Statement::Cmp) .SquirrelFunc(_SC("_typename"), &Statement::Typename) .Func(_SC("_tostring"), &Statement::ToString) - /* Properties */ + // Properties .Prop(_SC("Valid"), &Statement::IsValid) .Prop(_SC("Refs"), &Statement::GetRefCount) .Prop(_SC("Conn"), &Statement::GetConnection) @@ -252,7 +252,7 @@ void RegisterAPI(HSQUIRRELVM vm) .Prop(_SC("Query"), &Statement::GetQuery) .Prop(_SC("Good"), &Statement::GetGood) .Prop(_SC("Done"), &Statement::GetDone) - /* Functions */ + // Member Methods .Func(_SC("Release"), &Statement::Release) .Func(_SC("Reset"), &Statement::Reset) .Func(_SC("Clear"), &Statement::Clear) @@ -299,14 +299,14 @@ void RegisterAPI(HSQUIRRELVM vm) ); sqlns.Bind(_SC("Column"), Class< Column >(vm, _SC("SqSQLiteColumn")) - /* Constructors */ + // Constructors .Ctor() .Ctor< const Column & >() - /* Metamethods */ + // Metamethods .Func(_SC("_cmp"), &Column::Cmp) .SquirrelFunc(_SC("_typename"), &Column::Typename) .Func(_SC("_tostring"), &Column::ToString) - /* Properties */ + // Properties .Prop(_SC("Valid"), &Column::IsValid) .Prop(_SC("Refs"), &Column::GetRefCount) .Prop(_SC("Index"), &Column::GetIndex) @@ -325,10 +325,19 @@ void RegisterAPI(HSQUIRRELVM vm) .Prop(_SC("OriginName"), &Column::GetOriginName) .Prop(_SC("Type"), &Column::GetType) .Prop(_SC("Bytes"), &Column::GetBytes) - /* Functions */ + // Member Methods .Func(_SC("Release"), &Column::Release) ); + sqlns.Bind(_SC("Transaction"), Class< Transaction, NoCopy< Transaction > >(vm, _SC("SqSQLiteTransaction")) + // Constructors + .Ctor< const Connection & >() + // Properties + .Prop(_SC("Committed"), &Transaction::Commited) + // Member Methods + .Func(_SC("Commit"), &Transaction::Commit) + ); + sqlns.Func(_SC("IsQueryEmpty"), &IsQueryEmpty); sqlns.Func(_SC("GetErrStr"), &GetErrStr); sqlns.Func(_SC("SetSoftHeapLimit"), &SetSoftHeapLimit); @@ -343,7 +352,7 @@ void RegisterAPI(HSQUIRRELVM vm) RootTable(vm).Bind(_SC("SQLite"), sqlns); -/* + ConstTable(vm).Enum(_SC("ESQLite"), Enumeration(vm) .Const(_SC("ABORT"), SQLITE_ABORT) .Const(_SC("ABORT_ROLLBACK"), SQLITE_ABORT_ROLLBACK) @@ -682,7 +691,6 @@ void RegisterAPI(HSQUIRRELVM vm) .Const(_SC("WARNING"), SQLITE_WARNING) .Const(_SC("WARNING_AUTOINDEX"), SQLITE_WARNING_AUTOINDEX) ); -*/ } // --------------------------------------------------------------------------------------------