GCC Code Coverage Report


Directory: libimgdoc2/
File: libimgdoc2/src/doc/documentMetadataBase.cpp
Date: 2025-02-03 12:41:04
Exec Total Coverage
Lines: 104 126 82.5%
Functions: 9 9 100.0%
Branches: 139 270 51.5%

Line Branch Exec Source
1 // SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH
2 //
3 // SPDX-License-Identifier: MIT
4
5 #include "documentMetadataBase.h"
6 #include <string>
7 #include "exceptions.h"
8 #include <gsl/narrow>
9
10 using namespace std;
11 using namespace imgdoc2;
12
13 630 DocumentMetadataBase::DatabaseDataTypeValue DocumentMetadataBase::DetermineDatabaseDataTypeValueOrThrow(imgdoc2::DocumentMetadataType type, const imgdoc2::IDocumentMetadata::metadata_item_variant& value)
14 {
15 630 const DatabaseDataTypeValue result = DetermineDatabaseDataTypeValue(type, value);
16
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 630 times.
630 if (result == DatabaseDataTypeValue::invalid)
17 {
18 throw imgdoc2::invalid_argument_exception("The value is invalid");
19 }
20
21 630 return result;
22 }
23
24 630 /*static*/DocumentMetadataBase::DatabaseDataTypeValue DocumentMetadataBase::DetermineDatabaseDataTypeValue(imgdoc2::DocumentMetadataType type, const imgdoc2::IDocumentMetadata::metadata_item_variant& value)
25 {
26
2/2
✓ Branch 1 taken 522 times.
✓ Branch 2 taken 108 times.
630 if (std::holds_alternative<monostate>(value))
27 {
28 522 return DatabaseDataTypeValue::null;
29 }
30
31
2/8
✗ Branch 0 not taken.
✓ Branch 1 taken 96 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
108 switch (type)
32 {
33 case DocumentMetadataType::kNull:
34 // in this the value does not matter
35 return DatabaseDataTypeValue::null;
36 96 case DocumentMetadataType::kText:
37 // in this case the value must contain a string
38
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 96 times.
96 if (!std::holds_alternative<std::string>(value))
39 {
40 throw invalid_argument_exception("The value must be a string");
41 }
42
43 96 return DatabaseDataTypeValue::utf8string;
44 case DocumentMetadataType::kInt32:
45 // in this case the value must contain an integer
46 if (!std::holds_alternative<int>(value))
47 {
48 throw invalid_argument_exception("The value must be an integer");
49 }
50
51 return DatabaseDataTypeValue::int32;
52 case DocumentMetadataType::kDouble:
53 // in this case the value must contain a double
54 if (!std::holds_alternative<double>(value))
55 {
56 throw invalid_argument_exception("The value must be a double");
57 }
58
59 return DatabaseDataTypeValue::doublefloat;
60 case DocumentMetadataType::kJson:
61 // in this case the value must contain a string
62 if (!std::holds_alternative<string>(value))
63 {
64 throw invalid_argument_exception("The value must be a string");
65 }
66
67 return DatabaseDataTypeValue::json;
68 12 case DocumentMetadataType::kDefault:
69
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 8 times.
12 if (std::holds_alternative<std::string>(value))
70 {
71 4 return DatabaseDataTypeValue::utf8string;
72 }
73
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 4 times.
8 else if (std::holds_alternative<int>(value))
74 {
75 4 return DatabaseDataTypeValue::int32;
76 }
77
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 else if (std::holds_alternative<double>(value))
78 {
79 4 return DatabaseDataTypeValue::doublefloat;
80 }
81 else
82 {
83 throw invalid_argument_exception("Unknown metadata item type");
84 }
85 case DocumentMetadataType::kInvalid:
86 throw invalid_argument_exception("The metadata type is invalid");
87 }
88
89 return DatabaseDataTypeValue::invalid;
90 }
91
92 628 int DocumentMetadataBase::BindTypeDiscriminatorAndData(
93 const std::shared_ptr<IDbStatement>& database_statement,
94 int binding_index,
95 DatabaseDataTypeValue type,
96 const imgdoc2::IDocumentMetadata::metadata_item_variant& value)
97 {
98 628 database_statement->BindInt32(binding_index++, gsl::narrow_cast<int>(type));
99
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 624 times.
628 if (std::holds_alternative<double>(value))
100 {
101 4 database_statement->BindDouble(binding_index++, std::get<double>(value));
102 }
103 else
104 {
105 624 database_statement->BindNull(binding_index++);
106 }
107
108
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 624 times.
628 if (std::holds_alternative<int32_t>(value))
109 {
110 4 database_statement->BindInt32(binding_index++, std::get<int>(value));
111 }
112 else
113 {
114 624 database_statement->BindNull(binding_index++);
115 }
116
117
2/2
✓ Branch 1 taken 100 times.
✓ Branch 2 taken 528 times.
628 if (std::holds_alternative<string>(value))
118 {
119 100 database_statement->BindString(binding_index++, std::get<string>(value));
120 }
121 else
122 {
123 528 database_statement->BindNull(binding_index++);
124 }
125
126 628 return binding_index;
127 }
128
129 200 /*static*/std::vector<std::string_view> DocumentMetadataBase::SplitPath(const std::string_view& path)
130 {
131 200 std::vector<std::string_view> tokens;
132 200 std::size_t start = 0, end;
133
2/2
✓ Branch 1 taken 588 times.
✓ Branch 2 taken 192 times.
780 while ((end = path.find(DocumentMetadataBase::kPathDelimiter_, start)) != std::string_view::npos)
134 {
135
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 580 times.
588 if (end == start)
136 {
137
1/2
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
8 throw invalid_path_exception("path must not contain zero-length fragments");
138 }
139
140
2/4
✓ Branch 1 taken 580 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 580 times.
✗ Branch 5 not taken.
580 tokens.push_back(path.substr(start, end - start));
141 580 start = end + 1;
142 }
143
144
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 190 times.
192 if (start == path.size())
145 {
146
1/2
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 throw invalid_path_exception("path must not end with a delimiter");
147 }
148
149
2/4
✓ Branch 1 taken 190 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 190 times.
✗ Branch 5 not taken.
190 tokens.push_back(path.substr(start));
150 190 return tokens;
151 10 }
152
153 190 std::shared_ptr<IDbStatement> DocumentMetadataBase::CreateQueryForNodeIdsForPath(const std::vector<std::string_view>& path_parts)
154 {
155
1/2
✓ Branch 1 taken 190 times.
✗ Branch 2 not taken.
190 ostringstream string_stream;
156
2/4
✓ Branch 3 taken 190 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 190 times.
✗ Branch 7 not taken.
190 const auto metadata_table_name = this->GetDocument()->GetDataBaseConfigurationCommon()->GetTableNameForMetadataTableOrThrow();
157
2/4
✓ Branch 3 taken 190 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 190 times.
✗ Branch 7 not taken.
190 const auto column_name_pk = this->GetDocument()->GetDataBaseConfigurationCommon()->GetColumnNameOfMetadataTableOrThrow(DatabaseConfigurationCommon::kMetadataTable_Column_Pk);
158
2/4
✓ Branch 3 taken 190 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 190 times.
✗ Branch 7 not taken.
190 const auto column_name_name = this->GetDocument()->GetDataBaseConfigurationCommon()->GetColumnNameOfMetadataTableOrThrow(DatabaseConfigurationCommon::kMetadataTable_Column_Name);
159
2/4
✓ Branch 3 taken 190 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 190 times.
✗ Branch 7 not taken.
190 const auto column_name_ancestor_id = this->GetDocument()->GetDataBaseConfigurationCommon()->GetColumnNameOfMetadataTableOrThrow(DatabaseConfigurationCommon::kMetadataTable_Column_AncestorId);
160
161
2/2
✓ Branch 1 taken 162 times.
✓ Branch 2 taken 28 times.
190 if (path_parts.size() > 1)
162 {
163 string_stream << "WITH RECURSIVE paths(id, name, level) AS( " <<
164 "SELECT " << column_name_pk << "," << column_name_name << ",1 FROM [" << metadata_table_name << "] WHERE " << column_name_ancestor_id << " IS NULL AND " << column_name_name << "=? " <<
165 "UNION " <<
166 "SELECT " << metadata_table_name << "." << column_name_pk << ", " << metadata_table_name << "." << column_name_name << ", level + 1 " <<
167 "FROM [" << metadata_table_name << "] JOIN paths WHERE " << metadata_table_name << "." << column_name_ancestor_id << "=paths.id AND " <<
168
30/60
✓ Branch 1 taken 162 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 162 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 162 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 162 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 162 times.
✗ Branch 14 not taken.
✓ Branch 16 taken 162 times.
✗ Branch 17 not taken.
✓ Branch 19 taken 162 times.
✗ Branch 20 not taken.
✓ Branch 22 taken 162 times.
✗ Branch 23 not taken.
✓ Branch 25 taken 162 times.
✗ Branch 26 not taken.
✓ Branch 28 taken 162 times.
✗ Branch 29 not taken.
✓ Branch 31 taken 162 times.
✗ Branch 32 not taken.
✓ Branch 34 taken 162 times.
✗ Branch 35 not taken.
✓ Branch 37 taken 162 times.
✗ Branch 38 not taken.
✓ Branch 40 taken 162 times.
✗ Branch 41 not taken.
✓ Branch 43 taken 162 times.
✗ Branch 44 not taken.
✓ Branch 46 taken 162 times.
✗ Branch 47 not taken.
✓ Branch 49 taken 162 times.
✗ Branch 50 not taken.
✓ Branch 52 taken 162 times.
✗ Branch 53 not taken.
✓ Branch 55 taken 162 times.
✗ Branch 56 not taken.
✓ Branch 58 taken 162 times.
✗ Branch 59 not taken.
✓ Branch 61 taken 162 times.
✗ Branch 62 not taken.
✓ Branch 64 taken 162 times.
✗ Branch 65 not taken.
✓ Branch 67 taken 162 times.
✗ Branch 68 not taken.
✓ Branch 70 taken 162 times.
✗ Branch 71 not taken.
✓ Branch 73 taken 162 times.
✗ Branch 74 not taken.
✓ Branch 76 taken 162 times.
✗ Branch 77 not taken.
✓ Branch 79 taken 162 times.
✗ Branch 80 not taken.
✓ Branch 82 taken 162 times.
✗ Branch 83 not taken.
✓ Branch 85 taken 162 times.
✗ Branch 86 not taken.
✓ Branch 88 taken 162 times.
✗ Branch 89 not taken.
162 "CASE level ";
169
170
2/2
✓ Branch 1 taken 568 times.
✓ Branch 2 taken 162 times.
730 for (size_t i = 1; i < path_parts.size(); i++)
171 {
172
7/14
✓ Branch 1 taken 568 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 568 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 568 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 568 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 568 times.
✗ Branch 14 not taken.
✓ Branch 16 taken 568 times.
✗ Branch 17 not taken.
✓ Branch 19 taken 568 times.
✗ Branch 20 not taken.
568 string_stream << "WHEN " << i << " THEN " << metadata_table_name << "." << column_name_name << "=? ";
173 }
174
175 string_stream << "END) " <<
176
2/4
✓ Branch 1 taken 162 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 162 times.
✗ Branch 5 not taken.
162 "SELECT id FROM paths;";
177 }
178
1/2
✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
28 else if (path_parts.size() == 1)
179 {
180
9/18
✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 28 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 28 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 28 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 28 times.
✗ Branch 14 not taken.
✓ Branch 16 taken 28 times.
✗ Branch 17 not taken.
✓ Branch 19 taken 28 times.
✗ Branch 20 not taken.
✓ Branch 22 taken 28 times.
✗ Branch 23 not taken.
✓ Branch 25 taken 28 times.
✗ Branch 26 not taken.
28 string_stream << "SELECT " << column_name_pk << " FROM " << metadata_table_name << " WHERE " << column_name_ancestor_id << " IS NULL AND " << column_name_name << "=?;";
181 }
182 else
183 {
184 throw invalid_argument_exception("The path must contain at least one part");
185 }
186
187
2/4
✓ Branch 4 taken 190 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 190 times.
✗ Branch 8 not taken.
190 auto statement = this->document_->GetDatabase_connection()->PrepareStatement(string_stream.str());
188 380 return statement;
189 190 }
190
191 110 std::vector<imgdoc2::dbIndex> DocumentMetadataBase::GetNodeIdsForPath(const std::string& path, size_t* count_of_parts_in_path)
192 {
193 // an empty string is legal (and means "the root"), ie. we return an empty vector
194
2/2
✓ Branch 1 taken 22 times.
✓ Branch 2 taken 88 times.
110 if (path.empty())
195 {
196
1/2
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
22 if (count_of_parts_in_path != nullptr)
197 {
198 22 *count_of_parts_in_path = 0;
199 }
200
201 22 return {};
202 }
203
204 // the path must NOT start with a slash
205
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 86 times.
88 if (path[0] == DocumentMetadataBase::kPathDelimiter_)
206 {
207
1/2
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 throw invalid_path_exception("The path must not start with a slash");
208 }
209
210
2/2
✓ Branch 2 taken 76 times.
✓ Branch 3 taken 10 times.
86 const std::vector<std::string_view> tokens = DocumentMetadataBase::SplitPath(path);
211
1/2
✓ Branch 0 taken 76 times.
✗ Branch 1 not taken.
76 if (count_of_parts_in_path != nullptr)
212 {
213 76 *count_of_parts_in_path = tokens.size();
214 }
215
216
1/2
✓ Branch 1 taken 76 times.
✗ Branch 2 not taken.
76 return this->GetNodeIdsForPathParts(tokens);
217 76 }
218
219 190 std::vector<imgdoc2::dbIndex> DocumentMetadataBase::GetNodeIdsForPathParts(const std::vector<std::string_view>& parts)
220 {
221
1/2
✓ Branch 1 taken 190 times.
✗ Branch 2 not taken.
190 const auto statement = this->CreateQueryForNodeIdsForPath(parts);
222
223 // TODO(JBl) : The binding currently is making a copy of the string. This is not necessary, we could use a "STATIC" binding
224 // if we ensure that the string is not deleted before the statement is executed.
225
2/2
✓ Branch 1 taken 758 times.
✓ Branch 2 taken 190 times.
948 for (size_t i = 0; i < parts.size(); i++)
226 {
227
2/4
✓ Branch 3 taken 758 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 758 times.
✗ Branch 7 not taken.
758 statement->BindStringView(gsl::narrow<int>(i + 1), parts[i]);
228 }
229
230 190 std::vector<imgdoc2::dbIndex> result;
231
1/2
✓ Branch 2 taken 190 times.
✗ Branch 3 not taken.
190 result.reserve(parts.size());
232
3/4
✓ Branch 5 taken 566 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 376 times.
✓ Branch 8 taken 190 times.
566 while (this->document_->GetDatabase_connection()->StepStatement(statement.get()))
233 {
234
1/2
✓ Branch 2 taken 376 times.
✗ Branch 3 not taken.
376 const imgdoc2::dbIndex index = statement->GetResultInt64(0);
235
1/2
✓ Branch 1 taken 376 times.
✗ Branch 2 not taken.
376 result.push_back(index);
236 }
237
238 380 return result;
239 190 }
240
241 110 bool DocumentMetadataBase::TryMapPathAndGetTerminalNode(const std::string& path, std::optional<imgdoc2::dbIndex>* terminal_node_id)
242 {
243 size_t count_of_parts_in_path;
244
2/2
✓ Branch 1 taken 98 times.
✓ Branch 2 taken 12 times.
110 const auto node_ids = this->GetNodeIdsForPath(path, &count_of_parts_in_path);
245
2/2
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 76 times.
98 if (count_of_parts_in_path == 0)
246 {
247 // this is a "special case", the path is empty, which means the "root"
248
1/2
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
22 if (terminal_node_id != nullptr)
249 {
250 22 *terminal_node_id = std::nullopt;
251 }
252
253 22 return true;
254 }
255
256
2/2
✓ Branch 1 taken 56 times.
✓ Branch 2 taken 20 times.
76 if (node_ids.size() == count_of_parts_in_path)
257 {
258
1/2
✓ Branch 0 taken 56 times.
✗ Branch 1 not taken.
56 if (terminal_node_id != nullptr)
259 {
260 56 *terminal_node_id = node_ids.back();
261 }
262
263 56 return true;
264 }
265
266 20 return false;
267 98 }
268
269 574 bool DocumentMetadataBase::CheckIfItemExists(imgdoc2::dbIndex primary_key)
270 {
271
1/2
✓ Branch 1 taken 574 times.
✗ Branch 2 not taken.
574 ostringstream string_stream;
272
2/4
✓ Branch 2 taken 574 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 574 times.
✗ Branch 6 not taken.
574 string_stream << "SELECT EXISTS(SELECT 1 FROM [" << this->GetDocument()->GetDataBaseConfigurationCommon()->GetTableNameForMetadataTableOrThrow() << "] " <<
273
8/16
✓ Branch 1 taken 574 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 574 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 574 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 574 times.
✗ Branch 12 not taken.
✓ Branch 16 taken 574 times.
✗ Branch 17 not taken.
✓ Branch 19 taken 574 times.
✗ Branch 20 not taken.
✓ Branch 22 taken 574 times.
✗ Branch 23 not taken.
✓ Branch 25 taken 574 times.
✗ Branch 26 not taken.
1148 "WHERE [" << this->GetDocument()->GetDataBaseConfigurationCommon()->GetColumnNameOfMetadataTableOrThrow(DatabaseConfigurationCommon::kMetadataTable_Column_Pk) << "]=?1)";
274
275
2/4
✓ Branch 5 taken 574 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 574 times.
✗ Branch 9 not taken.
574 const auto statement = this->GetDocument()->GetDatabase_connection()->PrepareStatement(string_stream.str());
276
1/2
✓ Branch 2 taken 574 times.
✗ Branch 3 not taken.
574 statement->BindInt64(1, primary_key);
277
278
2/4
✓ Branch 6 taken 574 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 574 times.
574 if (!this->GetDocument()->GetDatabase_connection()->StepStatement(statement.get()))
279 {
280 throw internal_error_exception("DocumentMetadataReader::CheckIfItemExists: Could not execute statement.");
281 }
282
283
1/2
✓ Branch 2 taken 574 times.
✗ Branch 3 not taken.
574 const int64_t result = statement->GetResultInt64(0);
284 574 return result == 1;
285 574 }
286