| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH | ||
| 2 | // | ||
| 3 | // SPDX-License-Identifier: MIT | ||
| 4 | |||
| 5 | #include <exceptions.h> | ||
| 6 | #include <sstream> | ||
| 7 | #include <memory> | ||
| 8 | #include <vector> | ||
| 9 | #include <string> | ||
| 10 | #include <utility> | ||
| 11 | #include "sqlite_DbConnection.h" | ||
| 12 | #include "sqlite_DbStatement.h" | ||
| 13 | #include "custom_functions.h" | ||
| 14 | #include "loglevel.h" | ||
| 15 | |||
| 16 | using namespace std; | ||
| 17 | using namespace imgdoc2; | ||
| 18 | |||
| 19 | 268 | /*static*/std::shared_ptr<IDbConnection> SqliteDbConnection::SqliteCreateNewDatabase(const char* filename, std::shared_ptr<imgdoc2::IHostingEnvironment> environment) | |
| 20 | { | ||
| 21 | 268 | sqlite3* database = nullptr; | |
| 22 |
1/2✓ Branch 1 taken 268 times.
✗ Branch 2 not taken.
|
268 | const int return_value = sqlite3_open_v2( |
| 23 | filename, | ||
| 24 | &database, | ||
| 25 | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI | SQLITE_OPEN_EXRESCODE, | ||
| 26 | nullptr); | ||
| 27 | |||
| 28 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 268 times.
|
268 | if (return_value != SQLITE_OK) |
| 29 | { | ||
| 30 | // TODO(JBL): error handling might be more involved here, c.f. https://www.sqlite.org/c3ref/open.html | ||
| 31 | ✗ | if (database != nullptr) | |
| 32 | { | ||
| 33 | // documentation states that even in case of an error, a database-connection object may be returned, which should be destroyed here | ||
| 34 | ✗ | sqlite3_close(database); | |
| 35 | } | ||
| 36 | |||
| 37 | ✗ | throw database_exception("Error from 'sqlite3_open_v2'", return_value); | |
| 38 | } | ||
| 39 | |||
| 40 |
1/2✓ Branch 1 taken 268 times.
✗ Branch 2 not taken.
|
268 | return make_shared<SqliteDbConnection>(database, environment); |
| 41 | } | ||
| 42 | |||
| 43 | 4 | /*static*/std::shared_ptr<IDbConnection> SqliteDbConnection::SqliteOpenExistingDatabase(const char* filename, bool readonly, std::shared_ptr<imgdoc2::IHostingEnvironment> environment) | |
| 44 | { | ||
| 45 | 4 | sqlite3* database = nullptr; | |
| 46 |
3/4✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
|
4 | const int return_value = sqlite3_open_v2( |
| 47 | filename, | ||
| 48 | &database, | ||
| 49 | (readonly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_READWRITE) | SQLITE_OPEN_URI | SQLITE_OPEN_EXRESCODE, | ||
| 50 | nullptr); | ||
| 51 | |||
| 52 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (return_value != SQLITE_OK) |
| 53 | { | ||
| 54 | // TODO(JBL): error handling might be more involved here, c.f. https://www.sqlite.org/c3ref/open.html | ||
| 55 | ✗ | if (database != nullptr) | |
| 56 | { | ||
| 57 | // documentation states that even in case of an error, a database-connection object may be returned, which should be destroyed here | ||
| 58 | ✗ | sqlite3_close(database); | |
| 59 | } | ||
| 60 | |||
| 61 | ✗ | throw database_exception("Error from 'sqlite3_open_v2'", return_value); | |
| 62 | } | ||
| 63 | |||
| 64 |
1/2✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
4 | return make_shared<SqliteDbConnection>(database, environment); |
| 65 | } | ||
| 66 | |||
| 67 | 272 | SqliteDbConnection::SqliteDbConnection(sqlite3* database, std::shared_ptr<imgdoc2::IHostingEnvironment> environment/*=nullptr*/) | |
| 68 | 272 | : environment_(std::move(environment)), database_(database), transaction_count_(0) | |
| 69 | { | ||
| 70 |
1/2✓ Branch 1 taken 272 times.
✗ Branch 2 not taken.
|
272 | SqliteCustomFunctions::SetupCustomQueries(database); |
| 71 | 272 | } | |
| 72 | |||
| 73 | 544 | /*virtual*/SqliteDbConnection::~SqliteDbConnection() | |
| 74 | { | ||
| 75 | // Note: calling "sqlite3_close_v2" with nullptr is harmless | ||
| 76 | 544 | sqlite3_close_v2(this->database_); | |
| 77 | 544 | } | |
| 78 | |||
| 79 | 85340 | /*virtual*/void SqliteDbConnection::Execute(const char* sql_statement) | |
| 80 | { | ||
| 81 | // https://www.sqlite.org/c3ref/exec.html | ||
| 82 | 85340 | const int return_value = sqlite3_exec(this->database_, sql_statement, nullptr, nullptr, nullptr); | |
| 83 | 85340 | this->LogSqlExecution("sqlite3_exec", sql_statement, return_value); | |
| 84 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 85340 times.
|
85340 | if (return_value != SQLITE_OK) |
| 85 | { | ||
| 86 | ✗ | throw database_exception("Error from 'sqlite3_exec'", return_value); | |
| 87 | } | ||
| 88 | 85340 | } | |
| 89 | |||
| 90 | 103138 | /*virtual*/void SqliteDbConnection::Execute(IDbStatement* statement, std::int64_t* number_of_rows_modified/*=nullptr*/) | |
| 91 | { | ||
| 92 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 103138 times.
|
103138 | if (statement == nullptr) |
| 93 | { | ||
| 94 | ✗ | throw invalid_argument("The argument 'statement' must not be null."); | |
| 95 | } | ||
| 96 | |||
| 97 |
1/2✓ Branch 0 taken 103138 times.
✗ Branch 1 not taken.
|
103138 | auto* sqlite_statement = dynamic_cast<ISqliteDbStatement*>(statement); |
| 98 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 103138 times.
|
103138 | if (sqlite_statement == nullptr) |
| 99 | { | ||
| 100 | ✗ | throw imgdoc2_exception("Incorrect type encountered - object does not implement 'ISqliteDbStatement'-interface."); | |
| 101 | } | ||
| 102 | |||
| 103 | 103138 | const int return_value = sqlite3_step(sqlite_statement->GetSqliteSqlStatement()); | |
| 104 | 103138 | this->LogSqlExecution("sqlite3_step", sqlite_statement->GetSqliteSqlStatement(), return_value); | |
| 105 | |||
| 106 | // see https://www.sqlite.org/c3ref/step.html | ||
| 107 | // Note that we intend that Execute-methods are used only for commands which do not return data, | ||
| 108 | // so this means that we do not expect 'SQLITE_ROW' here | ||
| 109 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 103138 times.
|
103138 | if (return_value != SQLITE_DONE) |
| 110 | { | ||
| 111 | ✗ | throw database_exception("Error from 'sqlite3_step'", return_value); | |
| 112 | } | ||
| 113 | |||
| 114 |
2/2✓ Branch 0 taken 26 times.
✓ Branch 1 taken 103112 times.
|
103138 | if (number_of_rows_modified != nullptr) |
| 115 | { | ||
| 116 | 26 | *number_of_rows_modified = sqlite3_changes64(this->database_); | |
| 117 | } | ||
| 118 | 103138 | } | |
| 119 | |||
| 120 | 102444 | /*virtual*/std::int64_t SqliteDbConnection::ExecuteAndGetLastRowId(IDbStatement* statement) | |
| 121 | { | ||
| 122 | 102444 | this->Execute(statement); | |
| 123 | |||
| 124 | // https://www.sqlite.org/c3ref/last_insert_rowid.html | ||
| 125 | 102444 | const std::int64_t last_row_id = sqlite3_last_insert_rowid(this->database_); | |
| 126 | 102444 | return last_row_id; | |
| 127 | } | ||
| 128 | |||
| 129 | 110522 | /*virtual*/std::shared_ptr<IDbStatement> SqliteDbConnection::PrepareStatement(const std::string& sql_statement) | |
| 130 | { | ||
| 131 | 110522 | sqlite3_stmt* statement = nullptr; | |
| 132 | |||
| 133 | // https://www.sqlite.org/c3ref/prepare.html | ||
| 134 |
1/2✓ Branch 2 taken 110522 times.
✗ Branch 3 not taken.
|
110522 | const int return_value = sqlite3_prepare_v2( |
| 135 | this->database_, | ||
| 136 | sql_statement.c_str(), | ||
| 137 | -1, | ||
| 138 | &statement, | ||
| 139 | nullptr); | ||
| 140 |
2/4✓ Branch 0 taken 110522 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 110522 times.
|
110522 | if (return_value != SQLITE_OK || statement == nullptr) |
| 141 | { | ||
| 142 | ✗ | throw database_exception("Error from 'sqlite3_prepare_v2'", return_value); | |
| 143 | } | ||
| 144 | |||
| 145 |
1/2✓ Branch 1 taken 110522 times.
✗ Branch 2 not taken.
|
110522 | return make_shared<SqliteDbStatement>(statement); |
| 146 | } | ||
| 147 | |||
| 148 | 14160 | /*virtual*/bool SqliteDbConnection::StepStatement(IDbStatement* statement) | |
| 149 | { | ||
| 150 | // try to cast "statement" to ISqliteStatement | ||
| 151 |
1/2✓ Branch 0 taken 14160 times.
✗ Branch 1 not taken.
|
14160 | auto* sqlite_statement = dynamic_cast<ISqliteDbStatement*>(statement); |
| 152 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 14160 times.
|
14160 | if (sqlite_statement == nullptr) |
| 153 | { | ||
| 154 | ✗ | throw runtime_error("incorrect type"); | |
| 155 | } | ||
| 156 | |||
| 157 | 14160 | const int return_value = sqlite3_step(sqlite_statement->GetSqliteSqlStatement()); | |
| 158 | 14160 | this->LogSqlExecution("sqlite3_step", sqlite_statement->GetSqliteSqlStatement(), return_value); | |
| 159 | |||
| 160 | // https://www.sqlite.org/c3ref/step.html | ||
| 161 |
2/3✓ Branch 0 taken 13662 times.
✓ Branch 1 taken 498 times.
✗ Branch 2 not taken.
|
14160 | switch (return_value) |
| 162 | { | ||
| 163 | 13662 | case SQLITE_ROW: | |
| 164 | 13662 | return true; | |
| 165 | 498 | case SQLITE_DONE: | |
| 166 | 498 | return false; | |
| 167 | ✗ | default: | |
| 168 | ✗ | throw database_exception("Error from 'sqlite3_step'.", return_value); | |
| 169 | } | ||
| 170 | } | ||
| 171 | |||
| 172 | 41924 | /*virtual*/void SqliteDbConnection::BeginTransaction() | |
| 173 | { | ||
| 174 |
2/2✓ Branch 1 taken 4 times.
✓ Branch 2 taken 41920 times.
|
41924 | if (this->IsTransactionPending()) |
| 175 | { | ||
| 176 |
1/2✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
4 | throw database_exception("Call to 'BeginTransaction' where there is already a pending transaction."); |
| 177 | } | ||
| 178 | |||
| 179 | 41920 | this->Execute("BEGIN;"); | |
| 180 | 41920 | this->transaction_count_++; | |
| 181 | 41920 | } | |
| 182 | |||
| 183 | 41936 | /*virtual*/void SqliteDbConnection::EndTransaction(bool commit) | |
| 184 | { | ||
| 185 |
2/2✓ Branch 1 taken 16 times.
✓ Branch 2 taken 41920 times.
|
41936 | if (!this->IsTransactionPending()) |
| 186 | { | ||
| 187 |
1/2✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
|
16 | throw database_exception("Call to 'EndTransaction' where there is no pending transaction."); |
| 188 | } | ||
| 189 | |||
| 190 |
2/2✓ Branch 0 taken 41918 times.
✓ Branch 1 taken 2 times.
|
41920 | const char* sql_command = (commit ? "COMMIT;" : "ROLLBACK;"); |
| 191 | |||
| 192 | 41920 | this->Execute(sql_command); | |
| 193 | 41920 | this->transaction_count_--; | |
| 194 | 41920 | } | |
| 195 | |||
| 196 | 125778 | /*virtual*/bool SqliteDbConnection::IsTransactionPending() const | |
| 197 | { | ||
| 198 | 125778 | return this->transaction_count_ > 0; | |
| 199 | } | ||
| 200 | |||
| 201 | 64 | /*virtual*/std::vector<IDbConnection::ColumnInfo> SqliteDbConnection::GetTableInfo(const char* table_name) | |
| 202 | { | ||
| 203 |
1/2✓ Branch 1 taken 64 times.
✗ Branch 2 not taken.
|
64 | ostringstream string_stream; |
| 204 |
3/6✓ Branch 1 taken 64 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 64 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 64 times.
✗ Branch 8 not taken.
|
64 | string_stream << "SELECT name, type FROM pragma_table_info('" << table_name << "')"; |
| 205 |
2/4✓ Branch 1 taken 64 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 64 times.
✗ Branch 5 not taken.
|
64 | const auto statement = this->PrepareStatement(string_stream.str()); |
| 206 | |||
| 207 | 64 | vector<SqliteDbConnection::ColumnInfo> result; | |
| 208 |
3/4✓ Branch 2 taken 462 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 398 times.
✓ Branch 5 taken 64 times.
|
462 | while (this->StepStatement(statement.get())) |
| 209 | { | ||
| 210 | 398 | ColumnInfo column_info; | |
| 211 |
1/2✓ Branch 2 taken 398 times.
✗ Branch 3 not taken.
|
398 | column_info.column_name = statement->GetResultString(0); |
| 212 |
1/2✓ Branch 2 taken 398 times.
✗ Branch 3 not taken.
|
398 | column_info.column_type = statement->GetResultString(1); |
| 213 |
1/2✓ Branch 1 taken 398 times.
✗ Branch 2 not taken.
|
398 | result.emplace_back(column_info); |
| 214 | 398 | } | |
| 215 | |||
| 216 | 128 | return result; | |
| 217 | 64 | } | |
| 218 | |||
| 219 | 14 | /*virtual*/std::vector<IDbConnection::IndexInfo> SqliteDbConnection::GetIndicesOfTable(const char* table_name) | |
| 220 | { | ||
| 221 |
1/2✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
|
14 | ostringstream string_stream; |
| 222 |
3/6✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 14 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 14 times.
✗ Branch 8 not taken.
|
14 | string_stream << "SELECT name FROM pragma_index_list('" << table_name << "')"; |
| 223 |
2/4✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 14 times.
✗ Branch 5 not taken.
|
14 | const auto statement = this->PrepareStatement(string_stream.str()); |
| 224 | 14 | vector<SqliteDbConnection::IndexInfo> result; | |
| 225 |
3/4✓ Branch 2 taken 34 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 20 times.
✓ Branch 5 taken 14 times.
|
34 | while (this->StepStatement(statement.get())) |
| 226 | { | ||
| 227 | 20 | IndexInfo index_info; | |
| 228 |
1/2✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
|
20 | index_info.index_name = statement->GetResultString(0); |
| 229 |
1/2✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
|
20 | result.emplace_back(index_info); |
| 230 | 20 | } | |
| 231 | |||
| 232 | 28 | return result; | |
| 233 | 14 | } | |
| 234 | |||
| 235 | 202660 | /*virtual*/const std::shared_ptr<imgdoc2::IHostingEnvironment>& SqliteDbConnection::GetHostingEnvironment() const | |
| 236 | { | ||
| 237 | 202660 | return this->environment_; | |
| 238 | } | ||
| 239 | |||
| 240 | 117298 | void SqliteDbConnection::LogSqlExecution(const char* function_name, sqlite3_stmt* pStmt, int return_value) const | |
| 241 | { | ||
| 242 |
2/2✓ Branch 3 taken 4 times.
✓ Branch 4 taken 117294 times.
|
117298 | if (this->GetHostingEnvironment()->IsLogLevelActive(LogLevel::Sql)) |
| 243 | { | ||
| 244 | // https://www.sqlite.org/c3ref/expanded_sql.html -> may return NULL if the expansion fails (e.g. due to an OOM error) | ||
| 245 | // (sqlite3_free is the correct function to free the memory allocated by sqlite3_expanded_sql, and it does nothing if called with NULL) | ||
| 246 |
1/2✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
4 | const unique_ptr<char, decltype(sqlite3_free)*> up_expanded_sql_statement(sqlite3_expanded_sql(pStmt), sqlite3_free); |
| 247 |
1/2✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
4 | if (up_expanded_sql_statement != nullptr) |
| 248 | { | ||
| 249 |
1/2✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
4 | this->LogSqlExecution(function_name, up_expanded_sql_statement.get(), return_value); |
| 250 | } | ||
| 251 | else | ||
| 252 | { | ||
| 253 | ✗ | this->LogSqlExecution(function_name, "**expansion failed**", return_value); | |
| 254 | } | ||
| 255 | 4 | } | |
| 256 | 117298 | } | |
| 257 | |||
| 258 | 85344 | void SqliteDbConnection::LogSqlExecution(const char* function_name, const char* sql_statement, int return_value) const | |
| 259 | { | ||
| 260 |
2/2✓ Branch 3 taken 18 times.
✓ Branch 4 taken 85326 times.
|
85344 | if (this->GetHostingEnvironment()->IsLogLevelActive(LogLevel::Sql)) |
| 261 | { | ||
| 262 |
1/2✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
|
18 | ostringstream string_stream; |
| 263 |
9/18✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 18 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 18 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 18 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 18 times.
✗ Branch 14 not taken.
✓ Branch 16 taken 18 times.
✗ Branch 17 not taken.
✓ Branch 19 taken 18 times.
✗ Branch 20 not taken.
✓ Branch 22 taken 18 times.
✗ Branch 23 not taken.
✓ Branch 25 taken 18 times.
✗ Branch 26 not taken.
|
18 | string_stream << "[" << function_name << "] -> (" << return_value << ", " << sqlite3_errstr(return_value) << "): " << sql_statement; |
| 264 |
3/6✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 18 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 18 times.
✗ Branch 10 not taken.
|
18 | this->GetHostingEnvironment()->Log(LogLevel::Sql, string_stream.str().c_str()); |
| 265 | 18 | } | |
| 266 | 85344 | } | |
| 267 |