map/das-dn/third_party/sqlite/KompexSQLiteDatabase.cpp
2024-07-08 10:27:17 +08:00

540 lines
18 KiB
C++

/*
This file is part of Kompex SQLite Wrapper.
Copyright (c) 2008-2021 Sven Broeske
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <fstream>
#include <iostream>
#include <exception>
#include "KompexSQLiteDatabase.h"
#include "KompexSQLiteException.h"
namespace Kompex
{
SQLiteDatabase::SQLiteDatabase():
mDatabaseHandle(0),
mIsMemoryDatabaseActive(false),
mDatabaseFilenameUtf8(""),
mDatabaseFilenameUtf16(L"")
{
}
SQLiteDatabase::SQLiteDatabase(const char *filename, int flags, const char *zVfs):
mDatabaseHandle(0),
mIsMemoryDatabaseActive(false)
{
Open(filename, flags, zVfs);
}
SQLiteDatabase::SQLiteDatabase(const wchar_t *filename):
mDatabaseHandle(0),
mIsMemoryDatabaseActive(false)
{
Open(filename);
}
SQLiteDatabase::SQLiteDatabase(const std::string &filename, int flags, const char *zVfs):
mDatabaseHandle(0),
mIsMemoryDatabaseActive(false)
{
Open(filename, flags, zVfs);
}
SQLiteDatabase::~SQLiteDatabase()
{
Close();
}
void SQLiteDatabase::Open(const char *filename, int flags, const char *zVfs)
{
// close old db, if one exist
if(mDatabaseHandle)
Close();
if(sqlite3_open_v2(filename, &mDatabaseHandle, flags, zVfs) != SQLITE_OK)
KOMPEX_EXCEPT(sqlite3_errmsg(mDatabaseHandle), sqlite3_errcode(mDatabaseHandle));
sqlite3_extended_result_codes(mDatabaseHandle, true);
mDatabaseFilenameUtf8 = std::string(filename);
mDatabaseFilenameUtf16 = L"";
}
void SQLiteDatabase::Open(const std::string &filename, int flags, const char *zVfs)
{
// close old db, if one exist
if(mDatabaseHandle)
Close();
if(sqlite3_open_v2(filename.c_str(), &mDatabaseHandle, flags, zVfs) != SQLITE_OK)
KOMPEX_EXCEPT(sqlite3_errmsg(mDatabaseHandle), sqlite3_errcode(mDatabaseHandle));
sqlite3_extended_result_codes(mDatabaseHandle, true);
mDatabaseFilenameUtf8 = std::string(filename);
mDatabaseFilenameUtf16 = L"";
}
void SQLiteDatabase::Open(const wchar_t *filename)
{
// close old db, if one exist
if(mDatabaseHandle)
Close();
// standard usage: SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE
if(sqlite3_open16(filename, &mDatabaseHandle) != SQLITE_OK)
KOMPEX_EXCEPT(sqlite3_errmsg(mDatabaseHandle), sqlite3_errcode(mDatabaseHandle));
sqlite3_extended_result_codes(mDatabaseHandle, true);
mDatabaseFilenameUtf8 = "";
mDatabaseFilenameUtf16 = filename;
}
void SQLiteDatabase::Close()
{
// detach database if the database was moved into memory
if(mIsMemoryDatabaseActive)
{
if(sqlite3_exec(mDatabaseHandle, "DETACH DATABASE origin", 0, 0, 0) != SQLITE_OK)
KOMPEX_EXCEPT(sqlite3_errmsg(mDatabaseHandle), sqlite3_errcode(mDatabaseHandle));
}
// close the database
if(mDatabaseHandle && sqlite3_close(mDatabaseHandle) != SQLITE_OK)
{
KOMPEX_EXCEPT(sqlite3_errmsg(mDatabaseHandle), sqlite3_errcode(mDatabaseHandle));
}
else
{
mDatabaseHandle = 0;
mDatabaseFilenameUtf8 = "";
mDatabaseFilenameUtf16 = L"";
mIsMemoryDatabaseActive = false;
}
}
void SQLiteDatabase::TraceOutput(void *ptr, const char *sql)
{
std::cout << "trace: " << sql << std::endl;
}
void SQLiteDatabase::ProfileOutput(void* ptr, const char* sql, sqlite3_uint64 time)
{
std::cout << "profile: " << sql << std::endl;
std::cout << "profile time: " << time << std::endl;
}
void SQLiteDatabase::MoveDatabaseToMemory(UtfEncoding encoding)
{
if(!mIsMemoryDatabaseActive)
{
if(mDatabaseFilenameUtf8 == "" && mDatabaseFilenameUtf16 == L"")
{
KOMPEX_EXCEPT("No opened database! Please open a database first.", -1);
return;
}
sqlite3 *memoryDatabase;
if(mDatabaseFilenameUtf8 != "")
sqlite3_open(":memory:", &memoryDatabase);
else
sqlite3_open16(L":memory:", &memoryDatabase);
// create the in-memory schema from the origin database
sqlite3_exec(mDatabaseHandle, "BEGIN", 0, 0, 0);
if(encoding == UTF8)
{
sqlite3_exec(mDatabaseHandle, "SELECT sql FROM sqlite_master WHERE sql NOT NULL AND tbl_name != 'sqlite_sequence'", &Kompex::SQLiteDatabase::ProcessDDLRow, memoryDatabase, 0);
}
else if(encoding == UTF16)
{
struct sqlite3_stmt *statement;
if(sqlite3_prepare_v2(mDatabaseHandle, "SELECT sql FROM sqlite_master WHERE sql NOT NULL AND tbl_name != 'sqlite_sequence'", -1, &statement, 0) != SQLITE_OK)
CleanUpFailedMemoryDatabase(memoryDatabase, mDatabaseHandle, false, true, statement, sqlite3_errmsg(mDatabaseHandle), sqlite3_errcode(mDatabaseHandle));
bool resultsAvailable = true;
while(resultsAvailable)
{
switch(sqlite3_step(statement))
{
case SQLITE_ROW:
{
resultsAvailable = true;
struct sqlite3_stmt *transferStatement;
if(sqlite3_prepare16_v2(memoryDatabase, sqlite3_column_text16(statement, 0), -1, &transferStatement, 0) != SQLITE_OK)
{
sqlite3_finalize(transferStatement);
CleanUpFailedMemoryDatabase(memoryDatabase, mDatabaseHandle, false, true, statement, sqlite3_errmsg(memoryDatabase), sqlite3_errcode(memoryDatabase));
}
// SQLITE_DONE should always be returned
if(sqlite3_step(transferStatement) != SQLITE_DONE)
{
sqlite3_finalize(transferStatement);
CleanUpFailedMemoryDatabase(memoryDatabase, mDatabaseHandle, false, true, statement, sqlite3_errmsg(memoryDatabase), sqlite3_errcode(memoryDatabase));
}
sqlite3_finalize(transferStatement);
break;
}
case SQLITE_DONE:
resultsAvailable = false;
break;
case SQLITE_BUSY:
CleanUpFailedMemoryDatabase(memoryDatabase, mDatabaseHandle, false, true, statement, "SQLITE_BUSY", SQLITE_BUSY);
resultsAvailable = false;
break;
case SQLITE_ERROR:
CleanUpFailedMemoryDatabase(memoryDatabase, mDatabaseHandle, false, true, statement, sqlite3_errmsg(mDatabaseHandle), sqlite3_errcode(mDatabaseHandle));
resultsAvailable = false;
break;
default:
CleanUpFailedMemoryDatabase(memoryDatabase, mDatabaseHandle, false, true, statement, sqlite3_errmsg(mDatabaseHandle), sqlite3_errcode(mDatabaseHandle));
resultsAvailable = false;
}
}
sqlite3_finalize(statement);
}
sqlite3_exec(mDatabaseHandle, "COMMIT", 0, 0, 0);
// attach the origin database to the in-memory
if(mDatabaseFilenameUtf8 != "")
{
std::string sql = "ATTACH DATABASE '" + mDatabaseFilenameUtf8 + "' as origin";
if(sqlite3_exec(memoryDatabase, sql.c_str(), 0, 0, 0) != SQLITE_OK)
{
sqlite3_close(memoryDatabase);
KOMPEX_EXCEPT(sqlite3_errmsg(memoryDatabase), sqlite3_errcode(memoryDatabase));
}
}
else
{
struct sqlite3_stmt *statement;
std::wstring sql = L"ATTACH DATABASE '" + mDatabaseFilenameUtf16 + L"' as origin";
if(sqlite3_prepare16_v2(memoryDatabase, sql.c_str(), -1, &statement, 0) != SQLITE_OK)
CleanUpFailedMemoryDatabase(memoryDatabase, memoryDatabase, false, false, statement, sqlite3_errmsg(memoryDatabase), sqlite3_errcode(memoryDatabase));
if(sqlite3_step(statement) != SQLITE_DONE)
CleanUpFailedMemoryDatabase(memoryDatabase, memoryDatabase, false, false, statement, sqlite3_errmsg(memoryDatabase), sqlite3_errcode(memoryDatabase));
sqlite3_finalize(statement);
}
// copy the data from the origin database to the in-memory
sqlite3_exec(memoryDatabase, "BEGIN", 0, 0, 0);
if(encoding == UTF8)
{
sqlite3_exec(memoryDatabase, "SELECT name FROM origin.sqlite_master WHERE type='table'", &Kompex::SQLiteDatabase::ProcessDMLRow, memoryDatabase, 0);
}
else if(encoding == UTF16)
{
struct sqlite3_stmt *statement;
if(sqlite3_prepare_v2(memoryDatabase, "SELECT name FROM origin.sqlite_master WHERE type='table'", -1, &statement, 0) != SQLITE_OK)
CleanUpFailedMemoryDatabase(memoryDatabase, memoryDatabase, true, false, statement, sqlite3_errmsg(memoryDatabase), sqlite3_errcode(memoryDatabase));
bool resultsAvailable = true;
while(resultsAvailable)
{
switch(sqlite3_step(statement))
{
case SQLITE_ROW:
{
resultsAvailable = true;
std::wstring tableName = (wchar_t*)sqlite3_column_text16(statement, 0);
std::wstring stmt = std::wstring(L"INSERT INTO main.") + tableName + std::wstring(L" SELECT * FROM origin.") + tableName;
struct sqlite3_stmt *transferStatement;
if(sqlite3_prepare16_v2(memoryDatabase, stmt.c_str(), -1, &transferStatement, 0) != SQLITE_OK)
{
sqlite3_finalize(transferStatement);
CleanUpFailedMemoryDatabase(memoryDatabase, memoryDatabase, true, true, statement, sqlite3_errmsg(memoryDatabase), sqlite3_errcode(memoryDatabase));
}
// SQLITE_DONE should always be returned
if(sqlite3_step(transferStatement) != SQLITE_DONE)
{
sqlite3_finalize(transferStatement);
CleanUpFailedMemoryDatabase(memoryDatabase, memoryDatabase, true, true, statement, sqlite3_errmsg(memoryDatabase), sqlite3_errcode(memoryDatabase));
}
sqlite3_finalize(transferStatement);
break;
}
case SQLITE_DONE:
resultsAvailable = false;
break;
case SQLITE_BUSY:
CleanUpFailedMemoryDatabase(memoryDatabase, memoryDatabase, true, true, statement, "SQLITE_BUSY", SQLITE_BUSY);
resultsAvailable = false;
break;
case SQLITE_ERROR:
CleanUpFailedMemoryDatabase(memoryDatabase, memoryDatabase, true, true, statement, sqlite3_errmsg(memoryDatabase), sqlite3_errcode(memoryDatabase));
resultsAvailable = false;
break;
default:
CleanUpFailedMemoryDatabase(memoryDatabase, memoryDatabase, true, true, statement, sqlite3_errmsg(memoryDatabase), sqlite3_errcode(memoryDatabase));
resultsAvailable = false;
}
}
sqlite3_finalize(statement);
}
if(sqlite3_exec(memoryDatabase, "COMMIT", 0, 0, 0) == SQLITE_OK)
{
sqlite3_close(mDatabaseHandle);
mDatabaseHandle = memoryDatabase;
mIsMemoryDatabaseActive = true;
}
else
{
CleanUpFailedMemoryDatabase(memoryDatabase, memoryDatabase, true, true, 0, sqlite3_errmsg(memoryDatabase), sqlite3_errcode(memoryDatabase));
}
}
}
void SQLiteDatabase::CleanUpFailedMemoryDatabase(sqlite3 *memoryDatabase, sqlite3 *rollbackDatabase, bool isDetachNecessary, bool isRollbackNecessary, sqlite3_stmt *stmt, const std::string &errMsg, int internalSqliteErrCode)
{
if(stmt != 0)
sqlite3_finalize(stmt);
if(isRollbackNecessary)
sqlite3_exec(rollbackDatabase, "ROLLBACK", 0, 0, 0);
if(isDetachNecessary)
sqlite3_exec(memoryDatabase, "DETACH DATABASE origin", 0, 0, 0);
sqlite3_close(memoryDatabase);
KOMPEX_EXCEPT(errMsg, internalSqliteErrCode);
}
int SQLiteDatabase::ProcessDDLRow(void *db, int columnsCount, char **values, char **columns)
{
if(columnsCount != 1)
{
KOMPEX_EXCEPT("error occured during DDL: columnsCount != 1", -1);
return -1;
}
// execute a sql statement in values[0] in the database db.
if(sqlite3_exec(static_cast<sqlite3*>(db), values[0], 0, 0, 0) != SQLITE_OK)
KOMPEX_EXCEPT("error occured during DDL: sqlite3_exec (error message: " + std::string(sqlite3_errmsg(static_cast<sqlite3*>(db))) + ")", sqlite3_errcode(static_cast<sqlite3*>(db)));
return 0;
}
int SQLiteDatabase::ProcessDMLRow(void *db, int columnsCount, char **values, char **columns)
{
if(columnsCount != 1)
{
KOMPEX_EXCEPT("error occured during DML: columnsCount != 1", -1);
return -1;
}
char *stmt = sqlite3_mprintf("INSERT INTO main.%q SELECT * FROM origin.%q", values[0], values[0]);
if(sqlite3_exec(static_cast<sqlite3*>(db), stmt, 0, 0, 0) != SQLITE_OK)
KOMPEX_EXCEPT("error occured during DDL: sqlite3_exec (error message: " + std::string(sqlite3_errmsg(static_cast<sqlite3*>(db))) + ")", sqlite3_errcode(static_cast<sqlite3*>(db)));
sqlite3_free(stmt);
return 0;
}
void SQLiteDatabase::SaveDatabaseFromMemoryToFile(const std::string &filename)
{
if(mIsMemoryDatabaseActive)
{
sqlite3 *fileDatabase;
if(filename == "")
{
if(mDatabaseFilenameUtf8 != "")
{
if(sqlite3_open_v2(mDatabaseFilenameUtf8.c_str(), &fileDatabase, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 0) != SQLITE_OK)
KOMPEX_EXCEPT(sqlite3_errmsg(fileDatabase), sqlite3_errcode(fileDatabase));
}
else
{
if(sqlite3_open16(mDatabaseFilenameUtf16.c_str(), &fileDatabase) != SQLITE_OK)
KOMPEX_EXCEPT(sqlite3_errmsg(fileDatabase), sqlite3_errcode(fileDatabase));
}
}
else
{
if(sqlite3_open_v2(filename.c_str(), &fileDatabase, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 0) != SQLITE_OK)
KOMPEX_EXCEPT(sqlite3_errmsg(fileDatabase), sqlite3_errcode(fileDatabase));
}
TakeSnapshot(fileDatabase);
}
}
void SQLiteDatabase::SaveDatabaseFromMemoryToFile(const wchar_t *filename)
{
if(mIsMemoryDatabaseActive)
{
sqlite3 *fileDatabase;
if(sqlite3_open16(filename, &fileDatabase) != SQLITE_OK)
KOMPEX_EXCEPT(sqlite3_errmsg(fileDatabase), sqlite3_errcode(fileDatabase));
TakeSnapshot(fileDatabase);
}
}
void SQLiteDatabase::TakeSnapshot(sqlite3 *destinationDatabase)
{
sqlite3_backup *backup;
backup = sqlite3_backup_init(destinationDatabase, "main", mDatabaseHandle, "main");
if(backup)
{
// -1 to copy the entire source database to the destination
if(sqlite3_backup_step(backup, -1) != SQLITE_DONE)
{
// save error message and error number - otherwise an error in sqlite3_backup_finish() could overwrite them
std::string errmsg = sqlite3_errmsg(destinationDatabase);
//int errcode = sqlite3_errcode(destinationDatabase);
// there must be exactly one call to sqlite3_backup_finish() for each successful call to sqlite3_backup_init()
sqlite3_backup_finish(backup);
KOMPEX_EXCEPT(sqlite3_errmsg(destinationDatabase), sqlite3_errcode(destinationDatabase));
}
// clean up resources allocated by sqlite3_backup_init()
if(sqlite3_backup_finish(backup) != SQLITE_OK)
KOMPEX_EXCEPT(sqlite3_errmsg(destinationDatabase), sqlite3_errcode(destinationDatabase));
}
if(sqlite3_close(destinationDatabase) != SQLITE_OK)
KOMPEX_EXCEPT(sqlite3_errmsg(destinationDatabase), sqlite3_errcode(destinationDatabase));
}
bool SQLiteDatabase::IsDatabaseReadOnly()
{
int result = sqlite3_db_readonly(mDatabaseHandle, "main");
if(result == -1)
KOMPEX_EXCEPT("'main' is not the name of a database on connection mDatabaseHandle", -1);
return !!result;
}
void SQLiteDatabase::CreateModule(const std::string &moduleName, const sqlite3_module *module, void *clientData, void(*xDestroy)(void*))
{
if(sqlite3_create_module_v2(mDatabaseHandle, moduleName.c_str(), module, clientData, xDestroy))
KOMPEX_EXCEPT(sqlite3_errmsg(mDatabaseHandle), sqlite3_errcode(mDatabaseHandle));
}
void SQLiteDatabase::DropModules(const char** keepModules)
{
if (sqlite3_drop_modules(mDatabaseHandle, keepModules))
KOMPEX_EXCEPT(sqlite3_errmsg(mDatabaseHandle), sqlite3_errcode(mDatabaseHandle));
}
int SQLiteDatabase::GetRuntimeStatusInformation(int operation, bool highwaterValue, bool resetFlag) const
{
int curValue;
int hiwtrValue;
if(sqlite3_db_status(mDatabaseHandle, operation, &curValue, &hiwtrValue, resetFlag) != SQLITE_OK)
KOMPEX_EXCEPT(sqlite3_errmsg(mDatabaseHandle), sqlite3_errcode(mDatabaseHandle));
if(highwaterValue)
return hiwtrValue;
return curValue;
}
int SQLiteDatabase::GetNumberOfCheckedOutLookasideMemorySlots() const
{
return GetRuntimeStatusInformation(SQLITE_DBSTATUS_LOOKASIDE_USED);
}
int SQLiteDatabase::GetHeapMemoryUsedByPagerCaches() const
{
return GetRuntimeStatusInformation(SQLITE_DBSTATUS_CACHE_USED);
}
int SQLiteDatabase::GetHeapMemoryUsedToStoreSchemas() const
{
return GetRuntimeStatusInformation(SQLITE_DBSTATUS_SCHEMA_USED);
}
int SQLiteDatabase::GetHeapAndLookasideMemoryUsedByPreparedStatements() const
{
return GetRuntimeStatusInformation(SQLITE_DBSTATUS_STMT_USED);
}
int SQLiteDatabase::GetPagerCacheHitCount() const
{
return GetRuntimeStatusInformation(SQLITE_DBSTATUS_CACHE_HIT);
}
int SQLiteDatabase::GetPagerCacheMissCount() const
{
return GetRuntimeStatusInformation(SQLITE_DBSTATUS_CACHE_MISS);
}
int SQLiteDatabase::GetNumberOfDirtyCacheEntries() const
{
return GetRuntimeStatusInformation(SQLITE_DBSTATUS_CACHE_WRITE);
}
int SQLiteDatabase::GetNumberOfUnresolvedForeignKeys() const
{
return GetRuntimeStatusInformation(SQLITE_DBSTATUS_DEFERRED_FKS);
}
int SQLiteDatabase::GetTransactionState(const char* schema) const
{
int txnState = sqlite3_txn_state(mDatabaseHandle, schema);
if(txnState == -1)
KOMPEX_EXCEPT("schema name is not valid", -1);
return txnState;
}
int SQLiteDatabase::GetHighestNumberOfCheckedOutLookasideMemorySlots(bool resetValue)
{
return GetRuntimeStatusInformation(SQLITE_DBSTATUS_LOOKASIDE_USED, true, resetValue);
}
int SQLiteDatabase::GetLookasideMemoryHitCount(bool resetValue)
{
return GetRuntimeStatusInformation(SQLITE_DBSTATUS_LOOKASIDE_HIT, true, resetValue);
}
int SQLiteDatabase::GetLookasideMemoryMissCountDueToSmallSlotSize(bool resetValue)
{
return GetRuntimeStatusInformation(SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE, true, resetValue);
}
int SQLiteDatabase::GetLookasideMemoryMissCountDueToFullMemory(bool resetValue)
{
return GetRuntimeStatusInformation(SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL, true, resetValue);
}
} // namespace Kompex