1
0
mirror of https://github.com/VCMP-SqMod/SqMod.git synced 2024-11-14 03:37:16 +01:00
SqMod/source/Library/SQLite/Connection.cpp

832 lines
25 KiB
C++
Raw Normal View History

#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 <get connection tag> 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 <set connection tag> using an invalid reference");
}
}
// ------------------------------------------------------------------------------------------------
SqObj & Connection::GetGlobalData() const
{
if (m_Handle)
{
return m_Handle->Data;
}
else
{
LogWrn("Attempting to <get connection data> using an invalid reference");
}
return NullData();
}
void Connection::SetGlobalData(SqObj & data) const
{
if (m_Handle)
{
m_Handle->Data = data;
}
else
{
LogWrn("Attempting to <set connection data> 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 <get connection status> using an invalid connection: null");
}
return 0;
}
// ------------------------------------------------------------------------------------------------
SQInt32 Connection::GetFlags() const
{
if (m_Handle)
{
return m_Handle->Flags;
}
else
{
LogWrn("Attempting to <get connection flags> 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 <get connection file path> 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 <get connection vfs string> using an invalid connection: null");
}
return _SC("");
}
// ------------------------------------------------------------------------------------------------
const SQChar * Connection::GetErrStr() const
{
if (m_Handle)
{
return m_Handle.ErrStr();
}
else
{
LogWrn("Attempting to <get connection error string> using an invalid connection: null");
}
return _SC("");
}
// ------------------------------------------------------------------------------------------------
const SQChar * Connection::GetErrMsg() const
{
if (m_Handle)
{
return m_Handle.ErrMsg();
}
else
{
LogWrn("Attempting to <get connection error message> 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 <execute database query> because: %s", m_Handle.ErrMsg());
}
else
{
return sqlite3_changes(m_Handle);
}
}
else
{
LogWrn("Attempting to <execute database query> 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 <queue database query> 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 <see if table exists> because : the statement could not be stepped");
// Return the requested information
return (sqlite3_column_int(stmt, 0) == 1);
}
}
else
{
LogErr("Unable to <see if table exists> because : the statement could not be created");
}
}
else
{
LogWrn("Attempting to <see if table exists> 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 <see if database is read only> because : 'main' is not the name of a database on connection");
}
// Return the result
else
{
return (result != 1);
}
}
else
{
LogWrn("Attempting to <see if database is read only> 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 <see if database has autocommit activated> 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 <get database changes> 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 <get database changes> 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 <get database total changes> using an invalid connection: null");
}
return SQMOD_UNKNOWN;
}
// ------------------------------------------------------------------------------------------------
SQInt32 Connection::GetErrorCode() const
{
if (m_Handle)
{
return sqlite3_errcode(m_Handle);
}
else
{
LogWrn("Attempting to <get database error code> 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 <get database extended error code> using an invalid connection: null");
}
return SQMOD_UNKNOWN;
}
// ------------------------------------------------------------------------------------------------
bool Connection::GetTracing() const
{
if (m_Handle)
{
return m_Handle->Trace;
}
else
{
LogWrn("Attempting to <see if database has tracing activated> 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 <activate database tracing> using an invalid connection: null");
}
}
// ------------------------------------------------------------------------------------------------
bool Connection::GetProfiling() const
{
if (m_Handle)
{
return m_Handle->Profile;
}
else
{
LogWrn("Attempting to <see if database has profiling activated> 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 <activate database profiling> 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 <set database busy timeout> because : %s", m_Handle.ErrMsg());
}
}
else
{
LogWrn("Attempting to <set database busy timeout> using an invalid connection: null");
}
}
// ------------------------------------------------------------------------------------------------
void Connection::InterruptOperation() const
{
if (m_Handle)
{
sqlite3_interrupt(m_Handle);
}
else
{
LogWrn("Attempting to <interrupt database operation> using an invalid connection: null");
}
}
// ------------------------------------------------------------------------------------------------
void Connection::ReleaseMemory() const
{
if (m_Handle)
{
sqlite3_db_release_memory(m_Handle);
}
else
{
LogWrn("Attempting to <release database memory> 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 <open in-memory database> 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 <begin database schema replication> 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 <replicate database schema> 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 <commit database schema replication> 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 <attach database origin> 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 <begin database data replication> 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 <replicate database data> 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 <commit database data replication> 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 <rollback database data replication> 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 <detach database origin> because : %s", db.ErrMsg());
}
}
else
{
// Return the new in-memory database
return db;
}
}
else if (!m_Handle)
{
LogWrn("Attempting to <copy database to memory> using an invalid connection: null");
}
else
{
LogWrn("Attempting to <copy database to memory> 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 <replicate database> 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 <open the specified database> because : %s", db.ErrMsg());
}
else
{
// Attempt to take snapshot of the database
TakeSnapshot(db);
// Return the database clone
return db;
}
}
else
{
LogWrn("Attempting to <replicate database> 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 <copy database source> 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 <finalize database backup> because: %s", sqlite3_errmsg(destination));
}
}
}
else if (!destination)
{
LogWrn("Attempting to <take database snapshot> using an invalid destination: null");
}
else
{
LogWrn("Attempting to <take database snapshot> 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 <get database runtime status information> because: %s", sqlite3_errmsg(m_Handle));
}
// Return what was requested
return highwater ? hiwtr_value : cur_value;
}
else
{
LogWrn("Attempting to <get database runtime status information> using an invalid connection: null");
}
return SQMOD_UNKNOWN;
}
} // Namespace:: SQLite
} // Namespace:: SqMod