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 |