5#if defined(DOTNETDUPE_USE_SQLITE)
13 struct SqliteDatabaseBackend::Impl :
public DotNetDupe::System::Object {
14 sqlite3* m_pDb =
nullptr;
15 std::string m_connectionString;
18 if (m_pDb) sqlite3_close(m_pDb);
22 static std::string ExtractSqliteDataSource(
const std::string& connStr) {
24 size_t srcIdx = connStr.find(
"Data Source=");
25 if (srcIdx == std::string::npos)
return "sqlite_default.db";
27 size_t endIdx = connStr.find(
";", srcIdx);
28 return (endIdx != std::string::npos) ? connStr.substr(srcIdx, endIdx - srcIdx) : connStr.substr(srcIdx);
31 SqliteDatabaseBackend::SqliteDatabaseBackend(
const DotNetDupe::System::String& connStr) : m_pImpl(SmartPointer<Impl>::NewShared()) {
34 m_pImpl->m_connectionString = connStrStd;
35 std::string filename = ExtractSqliteDataSource(connStrStd);
38 if (sqlite3_open(filename.c_str(), &m_pImpl->m_pDb) != SQLITE_OK) {
39 std::string errorMsg = m_pImpl->m_pDb ? sqlite3_errmsg(m_pImpl->m_pDb) :
"Failed to open SQLite database.";
40 if (m_pImpl->m_pDb) { sqlite3_close(m_pImpl->m_pDb); m_pImpl->m_pDb = nullptr; }
45 SqliteDatabaseBackend::~SqliteDatabaseBackend() {
48 void SqliteDatabaseBackend::ClearDatabase() {
52 static void BindSqliteParameters(sqlite3_stmt* pStmt,
const Collections::Generic::Dictionary<String, String>& parameters) {
54 for (
auto const& [paramName, paramVal] : parameters) {
55 std::string stdParamName = paramName.GetRawString() ? paramName.GetRawString() :
"";
56 std::string stdParamVal = paramVal.GetRawString() ? paramVal.GetRawString() :
"";
57 int idx = sqlite3_bind_parameter_index(pStmt, stdParamName.c_str());
58 if (idx > 0) sqlite3_bind_text(pStmt, idx, stdParamVal.c_str(), -1, SQLITE_TRANSIENT);
62 static void FetchSqliteRows(sqlite3_stmt* pStmt,
int colCount, Collections::Generic::List<Row>& resultRows,
int& rc) {
64 while (rc == SQLITE_ROW) {
66 for (
int i = 0; i < colCount; ++i) {
67 const unsigned char* valText = sqlite3_column_text(pStmt, i);
68 r.Values.Add(valText ?
String(
reinterpret_cast<const char*
>(valText)) :
String(
""));
70 resultRows.Add(std::move(r));
71 rc = sqlite3_step(pStmt);
75 static void CollectSqliteColumnNames(sqlite3_stmt* pStmt, Collections::Generic::List<String>& columnNames) {
77 int colCount = sqlite3_column_count(pStmt);
78 for (
int i = 0; i < colCount; ++i) {
79 const char* colName = sqlite3_column_name(pStmt, i);
84 static sqlite3_stmt* PrepareSqliteStatement(sqlite3* pDb,
const String& sql) {
86 if (!pDb)
throw InvalidOperationException(
"Database is not open.");
87 sqlite3_stmt* pStmt =
nullptr;
88 if (sqlite3_prepare_v2(pDb, sql.GetRawString() ? sql.GetRawString() :
"", -1, &pStmt,
nullptr) != SQLITE_OK) {
89 throw InvalidOperationException(sqlite3_errmsg(pDb));
94 static void FinalizeExecution(sqlite3* pDb, sqlite3_stmt* pStmt,
int rc,
int& rowsAffected) {
96 if (rc == SQLITE_DONE) rowsAffected = sqlite3_changes(pDb);
97 else { std::string err = sqlite3_errmsg(pDb); sqlite3_finalize(pStmt);
throw InvalidOperationException(err.c_str()); }
98 sqlite3_finalize(pStmt);
101 Collections::Generic::List<Row> SqliteDatabaseBackend::Execute(
103 const Collections::Generic::Dictionary<String, String>& parameters,
104 Collections::Generic::List<String>& columnNames,
108 rowsAffected = 0; columnNames.Clear();
109 sqlite3_stmt* pStmt = PrepareSqliteStatement(m_pImpl->m_pDb, sql);
112 BindSqliteParameters(pStmt, parameters);
113 int rc = sqlite3_step(pStmt);
114 CollectSqliteColumnNames(pStmt, columnNames);
117 Collections::Generic::List<Row> resultRows;
118 FetchSqliteRows(pStmt, sqlite3_column_count(pStmt), resultRows, rc);
119 FinalizeExecution(m_pImpl->m_pDb, pStmt, rc, rowsAffected);
Defines the exception thrown when a method call is invalid for the object's current state.
The exception that is thrown when a method call is invalid for the object's current state.
const char * GetRawString() const