#include "Library/SQLite/Connection.hpp" #include "Library/SQLite/Statement.hpp" namespace SqMod { namespace SQLite { // ------------------------------------------------------------------------------------------------ Connection::Connection() : m_Handle() { /* ... */ } // ------------------------------------------------------------------------------------------------ Connection::Connection(const SQChar * path) : Connection(path, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL) { /* ... */ } // ------------------------------------------------------------------------------------------------ Connection::Connection(const SQChar * path, SQInt32 flags) : Connection(path, flags, NULL) { /* ... */ } // ------------------------------------------------------------------------------------------------ Connection::Connection(const SQChar * path, SQInt32 flags, const SQChar * vfs) : m_Handle(path, flags, vfs) { /* ... */ } // ------------------------------------------------------------------------------------------------ Connection::Connection(const Handle & hnd) : m_Handle(hnd) { /* ... */ } // ------------------------------------------------------------------------------------------------ SQInteger Connection::Cmp(const Connection & o) const { if (m_Handle == o.m_Handle) { return 0; } else if (m_Handle && (m_Handle->Ptr > o.m_Handle->Ptr)) { return 1; } else { return -1; } } // ------------------------------------------------------------------------------------------------ const SQChar * Connection::ToString() const { if (m_Handle) { return m_Handle->Path.c_str(); } return _SC(""); } // ------------------------------------------------------------------------------------------------ const SQChar * Connection::GetGlobalTag() const { if (m_Handle) { return m_Handle->Tag.c_str(); } else { LogWrn("Attempting to using an invalid reference"); } return _SC(""); } void Connection::SetGlobalTag(const SQChar * tag) const { if (m_Handle) { m_Handle->Tag.assign(tag); } else { LogWrn("Attempting to using an invalid reference"); } } // ------------------------------------------------------------------------------------------------ SqObj & Connection::GetGlobalData() const { if (m_Handle) { return m_Handle->Data; } else { LogWrn("Attempting to using an invalid reference"); } return NullData(); } void Connection::SetGlobalData(SqObj & data) const { if (m_Handle) { m_Handle->Data = data; } else { LogWrn("Attempting to using an invalid reference"); } } // ------------------------------------------------------------------------------------------------ const SQChar * Connection::GetLocalTag() const { return m_Tag.c_str(); } void Connection::SetLocalTag(const SQChar * tag) { m_Tag = tag; } // ------------------------------------------------------------------------------------------------ SqObj & Connection::GetLocalData() { return m_Data; } void Connection::SetLocalData(SqObj & data) { m_Data = data; } // ------------------------------------------------------------------------------------------------ SQInt32 Connection::GetStatus() const { if (m_Handle) { return m_Handle->Status; } else { LogWrn("Attempting to using an invalid connection: null"); } return 0; } // ------------------------------------------------------------------------------------------------ SQInt32 Connection::GetFlags() const { if (m_Handle) { return m_Handle->Flags; } else { LogWrn("Attempting to using an invalid connection: null"); } return 0; } // ------------------------------------------------------------------------------------------------ const SQChar * Connection::GetPath() const { if (m_Handle) { return m_Handle->Path.c_str(); } else { LogWrn("Attempting to using an invalid connection: null"); } return _SC(""); } // ------------------------------------------------------------------------------------------------ const SQChar * Connection::GetVFS() const { if (m_Handle) { return m_Handle->VFS.c_str(); } else { LogWrn("Attempting to using an invalid connection: null"); } return _SC(""); } // ------------------------------------------------------------------------------------------------ const SQChar * Connection::GetErrStr() const { if (m_Handle) { return m_Handle.ErrStr(); } else { LogWrn("Attempting to using an invalid connection: null"); } return _SC(""); } // ------------------------------------------------------------------------------------------------ const SQChar * Connection::GetErrMsg() const { if (m_Handle) { return m_Handle.ErrMsg(); } else { LogWrn("Attempting to using an invalid connection: null"); } return _SC(""); } // ------------------------------------------------------------------------------------------------ SQInt32 Connection::Exec(const SQChar * str) { if (m_Handle) { if ((m_Handle->Status = sqlite3_exec(m_Handle, static_cast< const char * >(str), NULL, NULL, NULL)) != SQLITE_OK) { LogErr("Unable to because: %s", m_Handle.ErrMsg()); } else { return sqlite3_changes(m_Handle); } } else { LogWrn("Attempting to using an invalid connection: null"); } return SQMOD_UNKNOWN; } // ------------------------------------------------------------------------------------------------ void Connection::Queue(const SQChar * str) { if (m_Handle) { m_Handle->Queue.emplace(str); } else { LogWrn("Attempting to using an invalid connection: null"); } } // ------------------------------------------------------------------------------------------------ bool Connection::TableExists(const SQChar * name) const { if (m_Handle) { // Create the statement which counts the tables with the specified name Statement stmt(*this, "SELECT count(*) FROM [sqlite_master] WHERE [type]='table' AND [name]=?"); // See if the statement could be created if (stmt.IsValid()) { // Bind the table name stmt.IndexBindS(1, name); // Attempt to step the statement if (stmt.Step()) { LogErr("Unable to because : the statement could not be stepped"); // Return the requested information return (sqlite3_column_int(stmt, 0) == 1); } } else { LogErr("Unable to because : the statement could not be created"); } } else { LogWrn("Attempting to using an invalid connection: null"); } return false; } // ------------------------------------------------------------------------------------------------ bool Connection::IsReadOnly() const { if (m_Handle) { const int result = sqlite3_db_readonly(m_Handle, "main"); // Verify the result if(result == -1) { LogErr("Unable to because : 'main' is not the name of a database on connection"); } // Return the result else { return (result != 1); } } else { LogWrn("Attempting to using an invalid connection: null"); } // It's invalid so at least fall-back to read-only return true; } // ------------------------------------------------------------------------------------------------ bool Connection::GetAutoCommit() const { if (m_Handle) { return sqlite3_get_autocommit(m_Handle); } else if (!m_Handle) { LogWrn("Attempting to using an invalid connection: null"); } return false; } // ------------------------------------------------------------------------------------------------ SLongInt Connection::GetLastInsertRowID() const { if (m_Handle) { return SLongInt(static_cast< SLongInt::Type >(sqlite3_last_insert_rowid(m_Handle))); } else if (!m_Handle) { LogWrn("Attempting to using an invalid connection: null"); } return SLongInt(static_cast< SLongInt::Type >(SQMOD_UNKNOWN)); } // ------------------------------------------------------------------------------------------------ SQInt32 Connection::GetChanges() const { if (m_Handle) { return sqlite3_changes(m_Handle); } else if (!m_Handle) { LogWrn("Attempting to using an invalid connection: null"); } return SQMOD_UNKNOWN; } // ------------------------------------------------------------------------------------------------ SQInt32 Connection::GetTotalChanges() const { if (m_Handle) { return sqlite3_total_changes(m_Handle); } else if (!m_Handle) { LogWrn("Attempting to using an invalid connection: null"); } return SQMOD_UNKNOWN; } // ------------------------------------------------------------------------------------------------ SQInt32 Connection::GetErrorCode() const { if (m_Handle) { return sqlite3_errcode(m_Handle); } else { LogWrn("Attempting to using an invalid connection: null"); } return SQMOD_UNKNOWN; } // ------------------------------------------------------------------------------------------------ SQInt32 Connection::GetExtendedErrorCode() const { if (m_Handle) { return sqlite3_extended_errcode(m_Handle); } else { LogWrn("Attempting to using an invalid connection: null"); } return SQMOD_UNKNOWN; } // ------------------------------------------------------------------------------------------------ bool Connection::GetTracing() const { if (m_Handle) { return m_Handle->Trace; } else { LogWrn("Attempting to using an invalid connection: null"); } return false; } // ------------------------------------------------------------------------------------------------ void Connection::SetTracing(bool toggle) const { if (m_Handle && m_Handle->Trace != toggle) { if (m_Handle->Trace) { sqlite3_trace(m_Handle, NULL, NULL); } else { sqlite3_trace(m_Handle, &Connection::TraceOutput, NULL); } } else if (!m_Handle) { LogWrn("Attempting to using an invalid connection: null"); } } // ------------------------------------------------------------------------------------------------ bool Connection::GetProfiling() const { if (m_Handle) { return m_Handle->Profile; } else { LogWrn("Attempting to using an invalid connection: null"); } return false; } // ------------------------------------------------------------------------------------------------ void Connection::SetProfiling(bool toggle) const { if (m_Handle && m_Handle->Profile != toggle) { if (m_Handle->Profile) { sqlite3_profile(m_Handle, NULL, NULL); } else { sqlite3_profile(m_Handle, &Connection::ProfileOutput, NULL); } } else if (!m_Handle) { LogWrn("Attempting to using an invalid connection: null"); } } // ------------------------------------------------------------------------------------------------ void Connection::SetBusyTimeout(SQInteger millis) const { if (m_Handle) { if ((m_Handle->Status = sqlite3_busy_timeout(m_Handle, millis)) != SQLITE_OK) { LogErr("Unable to because : %s", m_Handle.ErrMsg()); } } else { LogWrn("Attempting to using an invalid connection: null"); } } // ------------------------------------------------------------------------------------------------ void Connection::InterruptOperation() const { if (m_Handle) { sqlite3_interrupt(m_Handle); } else { LogWrn("Attempting to using an invalid connection: null"); } } // ------------------------------------------------------------------------------------------------ void Connection::ReleaseMemory() const { if (m_Handle) { sqlite3_db_release_memory(m_Handle); } else { LogWrn("Attempting to using an invalid connection: null"); } } // ------------------------------------------------------------------------------------------------ SQInt32 Connection::GetNumberOfCheckedOutLookasideMemorySlots() const { return GetInfo(SQLITE_DBSTATUS_LOOKASIDE_USED); } // ------------------------------------------------------------------------------------------------ SQInt32 Connection::GetHeapMemoryUsedByPagerCaches() const { return GetInfo(SQLITE_DBSTATUS_CACHE_USED); } // ------------------------------------------------------------------------------------------------ SQInt32 Connection::GetHeapMemoryUsedToStoreSchemas() const { return GetInfo(SQLITE_DBSTATUS_SCHEMA_USED); } // ------------------------------------------------------------------------------------------------ SQInt32 Connection::GetHeapAndLookasideMemoryUsedByPreparedStatements() const { return GetInfo(SQLITE_DBSTATUS_STMT_USED); } // ------------------------------------------------------------------------------------------------ SQInt32 Connection::GetPagerCacheHitCount() const { return GetInfo(SQLITE_DBSTATUS_CACHE_HIT); } // ------------------------------------------------------------------------------------------------ SQInt32 Connection::GetPagerCacheMissCount() const { return GetInfo(SQLITE_DBSTATUS_CACHE_MISS); } // ------------------------------------------------------------------------------------------------ SQInt32 Connection::GetNumberOfDirtyCacheEntries() const { return GetInfo(SQLITE_DBSTATUS_CACHE_WRITE); } // ------------------------------------------------------------------------------------------------ SQInt32 Connection::GetNumberOfUnresolvedForeignKeys() const { return GetInfo(SQLITE_DBSTATUS_DEFERRED_FKS); } // ------------------------------------------------------------------------------------------------ SQInt32 Connection::GetHighestNumberOfCheckedOutLookasideMemorySlots(bool reset) { return GetInfo(SQLITE_DBSTATUS_LOOKASIDE_USED, true, reset); } // ------------------------------------------------------------------------------------------------ SQInt32 Connection::GetLookasideMemoryHitCount(bool reset) { return GetInfo(SQLITE_DBSTATUS_LOOKASIDE_HIT, true, reset); } // ------------------------------------------------------------------------------------------------ SQInt32 Connection::GetLookasideMemoryMissCountDueToSmallSlotSize(bool reset) { return GetInfo(SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE, true, reset); } // ------------------------------------------------------------------------------------------------ SQInt32 Connection::GetLookasideMemoryMissCountDueToFullMemory(bool reset) { return GetInfo(SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL, true, reset); } // ------------------------------------------------------------------------------------------------ Connection Connection::CopyToMemory() { if (m_Handle && !m_Handle->Memory) { // Attempt to open an in-memory database Handle db(":memory:", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL); // See if the database could be opened if (!db) { LogErr("Unable to because : %s", db.ErrMsg()); } // Begin a transaction to replicate the schema of origin database else if ((m_Handle->Status = sqlite3_exec(m_Handle, "BEGIN", NULL, NULL, NULL)) != SQLITE_OK) { LogErr("Unable to because : %s", m_Handle.ErrMsg()); } // Attempt to replicate the schema of origin database to the in-memory one else if ((m_Handle->Status = sqlite3_exec(m_Handle, "SELECT [sql] FROM [sqlite_master] WHERE [sql] NOT NULL AND [tbl_name] != 'sqlite_sequence'", &Connection::ProcessDDLRow, db, NULL)) != SQLITE_OK) { LogErr("Unable to because : %s", m_Handle.ErrMsg()); } // Attempt to commit the changes to the database schema replication else if ((m_Handle->Status = sqlite3_exec(m_Handle, "COMMIT", NULL, NULL, NULL)) != SQLITE_OK) { LogErr("Unable to because : %s", m_Handle.ErrMsg()); } // Attempt to attach the origin database to the in-memory one else if ((db->Status = sqlite3_exec(db, ToStringF("ATTACH DATABASE '%s' as origin", m_Handle->Path.c_str()), NULL, NULL, NULL)) != SQLITE_OK) { LogErr("Unable to because : %s", db.ErrMsg()); } // Begin a transaction to replicate the data of origin database else if ((db->Status = sqlite3_exec(db, "BEGIN", NULL, NULL, NULL) != SQLITE_OK)) { LogErr("Unable to because : %s", db.ErrMsg()); } // Attempt to replicate the data of origin database to the in-memory one else if ((db->Status = sqlite3_exec(db, "SELECT [name] FROM [origin.sqlite_master] WHERE [type]='table'", &Connection::ProcessDMLRow, db, NULL)) != SQLITE_OK) { LogErr("Unable to because : %s", m_Handle.ErrMsg()); } // Attempt to commit the changes to the database data replication else if ((db->Status = sqlite3_exec(db, "COMMIT", NULL, NULL, NULL)) != SQLITE_OK) { LogErr("Unable to because : %s", db.ErrMsg()); // Attempt to rollback changes from the data copy operation if ((db->Status = sqlite3_exec(db, "ROLLBACK", NULL, NULL, NULL)) != SQLITE_OK) { LogErr("Unable to because : %s", db.ErrMsg()); } // Attempt to detach the disk origin from in-memory database else if ((db->Status = sqlite3_exec(db, "DETACH DATABASE origin", NULL, NULL, NULL)) != SQLITE_OK) { LogErr("Unable to because : %s", db.ErrMsg()); } } else { // Return the new in-memory database return db; } } else if (!m_Handle) { LogWrn("Attempting to using an invalid connection: null"); } else { LogWrn("Attempting to for a database which is already in memory"); } // Fall-back to an invalid connection return Connection(); } // ------------------------------------------------------------------------------------------------ Connection Connection::CopyToDatabase(const SQChar * path) { if (m_Handle) { // See if the specified name is valid if (strlen(path) <= 0) { LogWrn("Attempting to using an invalid path: null"); } // Attempt to open the specified database Handle db(path, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL); // See if the database could be opened if (!db) { LogErr("Unable to because : %s", db.ErrMsg()); } else { // Attempt to take snapshot of the database TakeSnapshot(db); // Return the database clone return db; } } else { LogWrn("Attempting to using an invalid connection: null"); } // Fall-back to an invalid connection return Connection(); } // ------------------------------------------------------------------------------------------------ void Connection::TraceOutput(void * ptr, const char * sql) { SQMOD_UNUSED_VAR(ptr); LogInf("SQLite Trace: %s", sql); } // ------------------------------------------------------------------------------------------------ void Connection::ProfileOutput(void * ptr, const char * sql, sqlite3_uint64 time) { SQMOD_UNUSED_VAR(ptr); LogInf("SQLite profile (time: %llu): %s", time, sql); } // ------------------------------------------------------------------------------------------------ int Connection::ProcessDDLRow(void * db, int columns_count, char ** values, char ** columns) { SQMOD_UNUSED_VAR(columns); if(columns_count != 1) { LogErr("Error occurred during DDL: columns != 1"); // Operation failed return -1; } // Execute the sql statement in values[0] in the received database connection if(sqlite3_exec(static_cast< sqlite3 * >(db), values[0], NULL, NULL, NULL) != SQLITE_OK) { LogErr("Error occurred during DDL execution: %s", sqlite3_errmsg(static_cast< sqlite3 * >(db))); } // Operation succeeded return 0; } // ------------------------------------------------------------------------------------------------ int Connection::ProcessDMLRow(void * db, int columns_count, char ** values, char ** columns) { SQMOD_UNUSED_VAR(columns); if(columns_count != 1) { LogErr("Error occurred during DML: columns != 1"); // Operation failed return -1; } // Generate the query string with the received values char * sql = sqlite3_mprintf("INSERT INTO main.%q SELECT * FROM origin.%q", values[0], values[0]); // Attempt to execute the generated query string to the received database connection if(sqlite3_exec(static_cast< sqlite3 * >(db), sql, NULL, NULL, NULL) != SQLITE_OK) { LogErr("Error occurred during DML execution: %s", sqlite3_errmsg(static_cast< sqlite3 * >(db))); } // Free the generated query string sqlite3_free(sql); // Operation succeeded return 0; } // ------------------------------------------------------------------------------------------------ void Connection::TakeSnapshot(Handle & destination) { // Don't even bother to continue if there's no valid connection handle if (m_Handle && destination) { // Attempt to initialize a backup structure sqlite3_backup * backup = sqlite3_backup_init(destination, "main", m_Handle, "main"); // See if the backup structure could be created if(backup) { // -1 to copy the entire source database to the destination if((m_Handle->Status = sqlite3_backup_step(backup, -1)) != SQLITE_DONE) { LogErr("Unable to because: %s", sqlite3_errmsg(destination)); } // clean up resources allocated by sqlite3_backup_init() if((m_Handle->Status = sqlite3_backup_finish(backup)) != SQLITE_OK) { LogErr("Unable to because: %s", sqlite3_errmsg(destination)); } } } else if (!destination) { LogWrn("Attempting to using an invalid destination: null"); } else { LogWrn("Attempting to using an invalid connection: null"); } } // ------------------------------------------------------------------------------------------------ SQInt32 Connection::GetInfo(int operation, bool highwater, bool reset) const { // Don't even bother to continue if there's no valid connection handle if (m_Handle) { int cur_value; int hiwtr_value; // Attempt to retrieve the specified information if((m_Handle->Status = sqlite3_db_status(m_Handle, operation, &cur_value, &hiwtr_value, reset)) != SQLITE_OK) { LogErr("Unable to because: %s", sqlite3_errmsg(m_Handle)); } // Return what was requested return highwater ? hiwtr_value : cur_value; } else { LogWrn("Attempting to using an invalid connection: null"); } return SQMOD_UNKNOWN; } } // Namespace:: SQLite } // Namespace:: SqMod