GCC Code Coverage Report


Directory: libimgdoc2/
File: libimgdoc2/src/db/sqlite/sqlite_DbConnection.cpp
Date: 2025-02-03 12:41:04
Exec Total Coverage
Lines: 107 122 87.7%
Functions: 17 17 100.0%
Branches: 75 159 47.2%

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