Line | Branch | Exec | Source |
---|---|---|---|
1 | // SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH | ||
2 | // | ||
3 | // SPDX-License-Identifier: MIT | ||
4 | |||
5 | #include "documentMetadataWriter.h" | ||
6 | #include <vector> | ||
7 | #include <string> | ||
8 | #include "gsl/util" | ||
9 | |||
10 | using namespace std; | ||
11 | using namespace imgdoc2; | ||
12 | |||
13 | 634 | imgdoc2::dbIndex DocumentMetadataWriter::UpdateOrCreateItem( | |
14 | std::optional<imgdoc2::dbIndex> parent, | ||
15 | bool create_node_if_not_exists, | ||
16 | const string& name, | ||
17 | DocumentMetadataType type, | ||
18 | const IDocumentMetadata::metadata_item_variant& value) | ||
19 | { | ||
20 |
2/2✓ Branch 1 taken 630 times.
✓ Branch 2 taken 4 times.
|
634 | this->CheckNodeNameAndThrowIfInvalid(name); |
21 |
1/2✓ Branch 1 taken 630 times.
✗ Branch 2 not taken.
|
630 | const DatabaseDataTypeValue item_type = DocumentMetadataBase::DetermineDatabaseDataTypeValueOrThrow(type, value); |
22 | |||
23 |
8/10✓ Branch 1 taken 542 times.
✓ Branch 2 taken 88 times.
✓ Branch 4 taken 542 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 542 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 2 times.
✓ Branch 10 taken 540 times.
✓ Branch 11 taken 2 times.
✓ Branch 12 taken 628 times.
|
630 | if (parent.has_value() && !this->CheckIfItemExists(parent.value())) |
24 | { | ||
25 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
2 | ostringstream string_stream; |
26 |
4/8✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 2 times.
✗ Branch 11 not taken.
|
2 | string_stream << "The parent with pk=" << parent.value() << " does not exist."; |
27 |
3/6✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 2 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 2 times.
✗ Branch 9 not taken.
|
2 | throw non_existing_item_exception(string_stream.str(), parent.value()); |
28 | 2 | } | |
29 | |||
30 |
1/2✓ Branch 1 taken 628 times.
✗ Branch 2 not taken.
|
628 | auto statement = this->CreateStatementForUpdateOrCreateItemAndBindData(create_node_if_not_exists, parent, name, item_type, value); |
31 |
1/2✓ Branch 6 taken 628 times.
✗ Branch 7 not taken.
|
628 | this->GetDocument()->GetDatabase_connection()->Execute(statement.get()); |
32 | 628 | statement.reset(); | |
33 | |||
34 | // Ok this worked the new item was inserted or updated. Now we need to get the id of the item. | ||
35 | // TODO(Jbl): It is of course a bit unfortunate that we do another lookup here. However, I didn't find a | ||
36 | // a way around this (for all the cases we need to support). | ||
37 |
1/2✓ Branch 1 taken 628 times.
✗ Branch 2 not taken.
|
628 | const auto select_statement = this->CreateQueryForNameAndAncestorIdStatement(name, parent); |
38 | |||
39 | // we are expecting exactly one result, or zero in case of "not found" | ||
40 |
2/4✓ Branch 6 taken 628 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 628 times.
|
628 | if (!this->GetDocument()->GetDatabase_connection()->StepStatement(select_statement.get())) |
41 | { | ||
42 | ✗ | throw std::logic_error("Could not find the item we just inserted or updated"); | |
43 | } | ||
44 | |||
45 |
1/2✓ Branch 2 taken 628 times.
✗ Branch 3 not taken.
|
628 | const dbIndex pk_of_updated_or_created_item = select_statement->GetResultInt64(0); |
46 | |||
47 | 628 | return pk_of_updated_or_created_item; | |
48 | 628 | } | |
49 | |||
50 | 114 | imgdoc2::dbIndex DocumentMetadataWriter::UpdateOrCreateItemForPath( | |
51 | bool create_path_if_not_exists, | ||
52 | bool create_node_if_not_exists, | ||
53 | const std::string& path, | ||
54 | imgdoc2::DocumentMetadataType type, | ||
55 | const IDocumentMetadata::metadata_item_variant& value) | ||
56 | { | ||
57 |
1/2✓ Branch 2 taken 114 times.
✗ Branch 3 not taken.
|
114 | const auto path_parts = this->SplitPath(path); |
58 |
1/2✓ Branch 1 taken 114 times.
✗ Branch 2 not taken.
|
114 | auto pk_of_nodes_on_path = this->GetNodeIdsForPathParts(path_parts); |
59 | |||
60 | // If the size of the "pks of nodes on path" is smaller than the size of the path parts, then we | ||
61 | // need to create the missing nodes. Or - only if the caller requested it - we need to create the missing nodes. | ||
62 |
2/2✓ Branch 2 taken 36 times.
✓ Branch 3 taken 78 times.
|
114 | if (pk_of_nodes_on_path.size() < path_parts.size() - 1) |
63 | { | ||
64 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 36 times.
|
36 | if (!create_path_if_not_exists) |
65 | { | ||
66 | // TODO(JBl): find a better exception type | ||
67 | ✗ | throw std::invalid_argument("The path does not exist and the caller did not request to create it."); | |
68 | } | ||
69 | |||
70 |
1/2✓ Branch 1 taken 36 times.
✗ Branch 2 not taken.
|
36 | this->CreateMissingNodesOnPath(path_parts, pk_of_nodes_on_path); |
71 | } | ||
72 | |||
73 |
3/4✓ Branch 0 taken 8 times.
✓ Branch 1 taken 106 times.
✓ Branch 5 taken 114 times.
✗ Branch 6 not taken.
|
220 | return this->UpdateOrCreateItem( |
74 | 220 | pk_of_nodes_on_path.empty() ? optional<dbIndex>(nullopt) : optional<dbIndex>(pk_of_nodes_on_path.back()), | |
75 | create_node_if_not_exists, | ||
76 |
1/2✓ Branch 2 taken 114 times.
✗ Branch 3 not taken.
|
342 | string{ path_parts.back() }, |
77 | type, | ||
78 | 228 | value); | |
79 | 114 | } | |
80 | |||
81 | 30 | uint64_t DocumentMetadataWriter::DeleteItem( | |
82 | std::optional<imgdoc2::dbIndex> primary_key, | ||
83 | bool recursively) | ||
84 | { | ||
85 |
1/2✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
|
30 | const auto statement = this->CreateStatementForDeleteItemAndBindData(recursively, primary_key); |
86 | |||
87 | 30 | int64_t number_of_modified_rows = 0; | |
88 | |||
89 | // In the special case "primary_key=nullopt=root" and recursively=false we do not want to delete anything, | ||
90 | // and in this case "CreateStatementForDeleteItemAndBindData" returns 0. So, in this corner stone case, | ||
91 | // we want to do nothing and return 0. | ||
92 |
2/2✓ Branch 1 taken 26 times.
✓ Branch 2 taken 4 times.
|
30 | if (statement) |
93 | { | ||
94 |
1/2✓ Branch 6 taken 26 times.
✗ Branch 7 not taken.
|
26 | this->GetDocument()->GetDatabase_connection()->Execute(statement.get(), &number_of_modified_rows); |
95 | } | ||
96 | |||
97 | 60 | return gsl::narrow_cast<uint64_t>(number_of_modified_rows); | |
98 | 30 | } | |
99 | |||
100 | 16 | uint64_t DocumentMetadataWriter::DeleteItemForPath( | |
101 | const std::string& path, | ||
102 | bool recursively) | ||
103 | { | ||
104 | 16 | optional<imgdoc2::dbIndex> idx; | |
105 |
1/2✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
|
16 | const bool success = this->TryMapPathAndGetTerminalNode(path, &idx); |
106 |
1/2✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
|
16 | if (success) |
107 | { | ||
108 |
1/2✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
|
32 | return this->DeleteItem(idx, recursively); |
109 | } | ||
110 | |||
111 | // TODO(Jbl): find a better exception type | ||
112 | ✗ | throw runtime_error("DocumentMetadataReader::DeleteItemForPath"); | |
113 | } | ||
114 | |||
115 | 628 | std::shared_ptr<IDbStatement> DocumentMetadataWriter::CreateStatementForUpdateOrCreateItemAndBindData(bool create_node_if_not_exists, std::optional<imgdoc2::dbIndex> parent, const std::string& name, | |
116 | DatabaseDataTypeValue database_data_type_value, | ||
117 | const IDocumentMetadata::metadata_item_variant& value) | ||
118 | { | ||
119 | 628 | const bool parent_has_value = parent.has_value(); | |
120 |
1/2✓ Branch 1 taken 628 times.
✗ Branch 2 not taken.
|
628 | ostringstream string_stream; |
121 | |||
122 |
2/4✓ Branch 3 taken 628 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 628 times.
✗ Branch 7 not taken.
|
628 | const auto metadata_table_name = this->GetDocument()->GetDataBaseConfigurationCommon()->GetTableNameForMetadataTableOrThrow(); |
123 |
2/4✓ Branch 3 taken 628 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 628 times.
✗ Branch 7 not taken.
|
628 | const auto column_name_pk = this->GetDocument()->GetDataBaseConfigurationCommon()->GetColumnNameOfMetadataTableOrThrow(DatabaseConfigurationCommon::kMetadataTable_Column_Pk); |
124 |
2/4✓ Branch 3 taken 628 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 628 times.
✗ Branch 7 not taken.
|
628 | const auto column_name_name = this->GetDocument()->GetDataBaseConfigurationCommon()->GetColumnNameOfMetadataTableOrThrow(DatabaseConfigurationCommon::kMetadataTable_Column_Name); |
125 |
2/4✓ Branch 3 taken 628 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 628 times.
✗ Branch 7 not taken.
|
628 | const auto column_name_ancestor_id = this->GetDocument()->GetDataBaseConfigurationCommon()->GetColumnNameOfMetadataTableOrThrow(DatabaseConfigurationCommon::kMetadataTable_Column_AncestorId); |
126 |
2/4✓ Branch 3 taken 628 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 628 times.
✗ Branch 7 not taken.
|
628 | const auto column_name_type_discriminator = this->GetDocument()->GetDataBaseConfigurationCommon()->GetColumnNameOfMetadataTableOrThrow(DatabaseConfigurationCommon::kMetadataTable_Column_TypeDiscriminator); |
127 |
2/4✓ Branch 3 taken 628 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 628 times.
✗ Branch 7 not taken.
|
628 | const auto column_name_value_double = this->GetDocument()->GetDataBaseConfigurationCommon()->GetColumnNameOfMetadataTableOrThrow(DatabaseConfigurationCommon::kMetadataTable_Column_ValueDouble); |
128 |
2/4✓ Branch 3 taken 628 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 628 times.
✗ Branch 7 not taken.
|
628 | const auto column_name_value_integer = this->GetDocument()->GetDataBaseConfigurationCommon()->GetColumnNameOfMetadataTableOrThrow(DatabaseConfigurationCommon::kMetadataTable_Column_ValueInteger); |
129 |
2/4✓ Branch 3 taken 628 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 628 times.
✗ Branch 7 not taken.
|
628 | const auto column_name_value_string = this->GetDocument()->GetDataBaseConfigurationCommon()->GetColumnNameOfMetadataTableOrThrow(DatabaseConfigurationCommon::kMetadataTable_Column_ValueString); |
130 | |||
131 | // CAUTION: it seems if we want to check for a NULL, we cannot simply use the "=" operator, but need to use the "IS" operator. | ||
132 | // This is because the "=" operator does not work for NULL values, and it means that we cannot use data-binding, we have to modify | ||
133 | // the query string itself. This is not a problem, but it is something to keep in mind. | ||
134 | // TODO(Jbl): maybe there is a better way to do this? | ||
135 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 628 times.
|
628 | if (create_node_if_not_exists == false) |
136 | { | ||
137 | string_stream << "UPDATE [" << metadata_table_name << "] SET " << | ||
138 | "[" << column_name_type_discriminator << "] = ?3, " << | ||
139 | "[" << column_name_value_double << "] = ?4, " << | ||
140 | "[" << column_name_value_integer << "] = ?5, " << | ||
141 | "[" << column_name_value_string << "] = ?6 " << | ||
142 | ✗ | "WHERE [" << column_name_name << "] = ?1 AND "; | |
143 | ✗ | if (parent_has_value) | |
144 | { | ||
145 | ✗ | string_stream << "[" << column_name_ancestor_id << "] = ?2"; | |
146 | } | ||
147 | else | ||
148 | { | ||
149 | ✗ | string_stream << "[" << column_name_ancestor_id << "] IS NULL"; | |
150 | } | ||
151 | } | ||
152 | else | ||
153 | { | ||
154 | string_stream << "INSERT INTO [" << metadata_table_name << "] (" << | ||
155 | "[" << column_name_name << "]," << | ||
156 | "[" << column_name_ancestor_id << "]," << | ||
157 | "[" << column_name_type_discriminator << "]," << | ||
158 | "[" << column_name_value_double << "]," << | ||
159 | "[" << column_name_value_integer << "]," << | ||
160 | "[" << column_name_value_string << "]) " << | ||
161 | "VALUES(" << "?1, ?2, ?3, ?4, ?5, ?6" << ") " << | ||
162 | // There is a constraint on the table that ensures that the combination of name and ancestor id is unique. | ||
163 | // So, if the insert fails because of a constraint violation, we update the existing row. | ||
164 | "ON CONFLICT([" << column_name_name << "], [" << column_name_ancestor_id << "]) DO UPDATE " << | ||
165 | "SET [" << column_name_type_discriminator << "] = ?3, " << | ||
166 | "[" << column_name_value_double << "] = ?4, " << | ||
167 | "[" << column_name_value_integer << "] = ?5, " << | ||
168 | "[" << column_name_value_string << "] = ?6 " << | ||
169 |
44/88✓ Branch 1 taken 628 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 628 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 628 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 628 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 628 times.
✗ Branch 14 not taken.
✓ Branch 16 taken 628 times.
✗ Branch 17 not taken.
✓ Branch 19 taken 628 times.
✗ Branch 20 not taken.
✓ Branch 22 taken 628 times.
✗ Branch 23 not taken.
✓ Branch 25 taken 628 times.
✗ Branch 26 not taken.
✓ Branch 28 taken 628 times.
✗ Branch 29 not taken.
✓ Branch 31 taken 628 times.
✗ Branch 32 not taken.
✓ Branch 34 taken 628 times.
✗ Branch 35 not taken.
✓ Branch 37 taken 628 times.
✗ Branch 38 not taken.
✓ Branch 40 taken 628 times.
✗ Branch 41 not taken.
✓ Branch 43 taken 628 times.
✗ Branch 44 not taken.
✓ Branch 46 taken 628 times.
✗ Branch 47 not taken.
✓ Branch 49 taken 628 times.
✗ Branch 50 not taken.
✓ Branch 52 taken 628 times.
✗ Branch 53 not taken.
✓ Branch 55 taken 628 times.
✗ Branch 56 not taken.
✓ Branch 58 taken 628 times.
✗ Branch 59 not taken.
✓ Branch 61 taken 628 times.
✗ Branch 62 not taken.
✓ Branch 64 taken 628 times.
✗ Branch 65 not taken.
✓ Branch 67 taken 628 times.
✗ Branch 68 not taken.
✓ Branch 70 taken 628 times.
✗ Branch 71 not taken.
✓ Branch 73 taken 628 times.
✗ Branch 74 not taken.
✓ Branch 76 taken 628 times.
✗ Branch 77 not taken.
✓ Branch 79 taken 628 times.
✗ Branch 80 not taken.
✓ Branch 82 taken 628 times.
✗ Branch 83 not taken.
✓ Branch 85 taken 628 times.
✗ Branch 86 not taken.
✓ Branch 88 taken 628 times.
✗ Branch 89 not taken.
✓ Branch 91 taken 628 times.
✗ Branch 92 not taken.
✓ Branch 94 taken 628 times.
✗ Branch 95 not taken.
✓ Branch 97 taken 628 times.
✗ Branch 98 not taken.
✓ Branch 100 taken 628 times.
✗ Branch 101 not taken.
✓ Branch 103 taken 628 times.
✗ Branch 104 not taken.
✓ Branch 106 taken 628 times.
✗ Branch 107 not taken.
✓ Branch 109 taken 628 times.
✗ Branch 110 not taken.
✓ Branch 112 taken 628 times.
✗ Branch 113 not taken.
✓ Branch 115 taken 628 times.
✗ Branch 116 not taken.
✓ Branch 118 taken 628 times.
✗ Branch 119 not taken.
✓ Branch 121 taken 628 times.
✗ Branch 122 not taken.
✓ Branch 124 taken 628 times.
✗ Branch 125 not taken.
✓ Branch 127 taken 628 times.
✗ Branch 128 not taken.
✓ Branch 130 taken 628 times.
✗ Branch 131 not taken.
|
628 | "WHERE [" << column_name_name << "] = ?1 AND "; |
170 |
2/2✓ Branch 0 taken 540 times.
✓ Branch 1 taken 88 times.
|
628 | if (parent_has_value) |
171 | { | ||
172 |
3/6✓ Branch 1 taken 540 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 540 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 540 times.
✗ Branch 8 not taken.
|
540 | string_stream << "[" << column_name_ancestor_id << "] = ?2"; |
173 | } | ||
174 | else | ||
175 | { | ||
176 |
3/6✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 88 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 88 times.
✗ Branch 8 not taken.
|
88 | string_stream << "[" << column_name_ancestor_id << "] IS NULL"; |
177 | } | ||
178 | } | ||
179 | |||
180 |
2/4✓ Branch 5 taken 628 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 628 times.
✗ Branch 9 not taken.
|
628 | auto statement = this->GetDocument()->GetDatabase_connection()->PrepareStatement(string_stream.str()); |
181 | |||
182 | 628 | int binding_index = 1; | |
183 |
1/2✓ Branch 2 taken 628 times.
✗ Branch 3 not taken.
|
628 | statement->BindString(binding_index++, name); |
184 |
2/2✓ Branch 0 taken 540 times.
✓ Branch 1 taken 88 times.
|
628 | if (parent_has_value) |
185 | { | ||
186 |
2/4✓ Branch 2 taken 540 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 540 times.
✗ Branch 6 not taken.
|
540 | statement->BindInt64(binding_index++, parent.value()); |
187 | } | ||
188 | else | ||
189 | { | ||
190 | 88 | ++binding_index; | |
191 | } | ||
192 | |||
193 |
1/2✓ Branch 1 taken 628 times.
✗ Branch 2 not taken.
|
628 | this->BindTypeDiscriminatorAndData(statement, binding_index, database_data_type_value, value); // this will bind 3 values |
194 | |||
195 | 1256 | return statement; | |
196 | 628 | } | |
197 | |||
198 | 628 | std::shared_ptr<IDbStatement> DocumentMetadataWriter::CreateQueryForNameAndAncestorIdStatement(const std::string& name, std::optional<imgdoc2::dbIndex> parent) | |
199 | { | ||
200 |
2/4✓ Branch 3 taken 628 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 628 times.
✗ Branch 7 not taken.
|
628 | const auto metadata_table_name = this->GetDocument()->GetDataBaseConfigurationCommon()->GetTableNameForMetadataTableOrThrow(); |
201 |
2/4✓ Branch 3 taken 628 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 628 times.
✗ Branch 7 not taken.
|
628 | const auto column_name_pk = this->GetDocument()->GetDataBaseConfigurationCommon()->GetColumnNameOfMetadataTableOrThrow(DatabaseConfigurationCommon::kMetadataTable_Column_Pk); |
202 |
2/4✓ Branch 3 taken 628 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 628 times.
✗ Branch 7 not taken.
|
628 | const auto column_name_name = this->GetDocument()->GetDataBaseConfigurationCommon()->GetColumnNameOfMetadataTableOrThrow(DatabaseConfigurationCommon::kMetadataTable_Column_Name); |
203 |
2/4✓ Branch 3 taken 628 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 628 times.
✗ Branch 7 not taken.
|
628 | const auto column_name_ancestor_id = this->GetDocument()->GetDataBaseConfigurationCommon()->GetColumnNameOfMetadataTableOrThrow(DatabaseConfigurationCommon::kMetadataTable_Column_AncestorId); |
204 | |||
205 | 628 | const bool parent_has_value = parent.has_value(); | |
206 |
1/2✓ Branch 1 taken 628 times.
✗ Branch 2 not taken.
|
628 | ostringstream string_stream; |
207 |
7/14✓ Branch 1 taken 628 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 628 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 628 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 628 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 628 times.
✗ Branch 14 not taken.
✓ Branch 16 taken 628 times.
✗ Branch 17 not taken.
✓ Branch 19 taken 628 times.
✗ Branch 20 not taken.
|
628 | string_stream << "SELECT [" << column_name_pk << "] FROM [" << metadata_table_name << "] WHERE [" << column_name_name << "]=?1 AND "; |
208 |
2/2✓ Branch 0 taken 540 times.
✓ Branch 1 taken 88 times.
|
628 | if (parent_has_value) |
209 | { | ||
210 |
3/6✓ Branch 1 taken 540 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 540 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 540 times.
✗ Branch 8 not taken.
|
540 | string_stream << "[" << column_name_ancestor_id << "] = ?2"; |
211 | } | ||
212 | else | ||
213 | { | ||
214 |
3/6✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 88 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 88 times.
✗ Branch 8 not taken.
|
88 | string_stream << "[" << column_name_ancestor_id << "] IS NULL"; |
215 | } | ||
216 | |||
217 |
1/2✓ Branch 1 taken 628 times.
✗ Branch 2 not taken.
|
628 | string_stream << ";"; |
218 | |||
219 |
2/4✓ Branch 5 taken 628 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 628 times.
✗ Branch 9 not taken.
|
628 | auto statement = this->GetDocument()->GetDatabase_connection()->PrepareStatement(string_stream.str()); |
220 |
1/2✓ Branch 2 taken 628 times.
✗ Branch 3 not taken.
|
628 | statement->BindString(1, name); |
221 |
2/2✓ Branch 0 taken 540 times.
✓ Branch 1 taken 88 times.
|
628 | if (parent_has_value) |
222 | { | ||
223 |
2/4✓ Branch 2 taken 540 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 540 times.
✗ Branch 6 not taken.
|
540 | statement->BindInt64(2, parent.value()); |
224 | } | ||
225 | |||
226 | 1256 | return statement; | |
227 | 628 | } | |
228 | |||
229 | /* | ||
230 | with recursive paths(id, name, path, level) as ( | ||
231 | select Pk, Name, Name, 0 from METADATA where AncestorId is null AND Name='Node1' | ||
232 | union | ||
233 | select METADATA.Pk, METADATA.name, paths.path || '/' || METADATA.name, level+1 | ||
234 | from METADATA join paths where METADATA.AncestorId = paths.id AND | ||
235 | ((level = 0 AND METADATA.Name="Node1_1") OR | ||
236 | (level = 1 AND METADATA.Name="Node1_1_2")) | ||
237 | ) | ||
238 | select id, path, level from paths | ||
239 | */ | ||
240 | |||
241 | 36 | void DocumentMetadataWriter::CreateMissingNodesOnPath(const std::vector<std::string_view>& path_parts, std::vector<imgdoc2::dbIndex>& pks_existing) | |
242 | { | ||
243 | 36 | const size_t count_existing_pks = pks_existing.size(); | |
244 |
2/2✓ Branch 1 taken 234 times.
✓ Branch 2 taken 36 times.
|
270 | for (size_t i = count_existing_pks; i < path_parts.size() - 1; ++i) |
245 | { | ||
246 |
3/4✓ Branch 1 taken 202 times.
✓ Branch 2 taken 32 times.
✓ Branch 6 taken 234 times.
✗ Branch 7 not taken.
|
670 | const auto new_node = this->UpdateOrCreateItem( |
247 | 202 | i > 0 ? std::optional<imgdoc2::dbIndex>(pks_existing[i - 1]) : std::optional<imgdoc2::dbIndex>(), | |
248 | true, | ||
249 |
1/2✓ Branch 2 taken 234 times.
✗ Branch 3 not taken.
|
702 | string{ path_parts[i] }, |
250 | DocumentMetadataType::kNull, | ||
251 | 234 | std::monostate()); | |
252 |
1/2✓ Branch 1 taken 234 times.
✗ Branch 2 not taken.
|
234 | pks_existing.push_back(new_node); |
253 | } | ||
254 | 36 | } | |
255 | |||
256 | 30 | std::shared_ptr<IDbStatement> DocumentMetadataWriter::CreateStatementForDeleteItemAndBindData(bool recursively, std::optional<imgdoc2::dbIndex> parent) | |
257 | { | ||
258 |
1/2✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
|
30 | ostringstream string_stream; |
259 | |||
260 |
2/4✓ Branch 3 taken 30 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 30 times.
✗ Branch 7 not taken.
|
30 | const auto metadata_table_name = this->GetDocument()->GetDataBaseConfigurationCommon()->GetTableNameForMetadataTableOrThrow(); |
261 |
2/4✓ Branch 3 taken 30 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 30 times.
✗ Branch 7 not taken.
|
30 | const auto column_name_pk = this->GetDocument()->GetDataBaseConfigurationCommon()->GetColumnNameOfMetadataTableOrThrow(DatabaseConfigurationCommon::kMetadataTable_Column_Pk); |
262 |
2/4✓ Branch 3 taken 30 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 30 times.
✗ Branch 7 not taken.
|
30 | const auto column_name_name = this->GetDocument()->GetDataBaseConfigurationCommon()->GetColumnNameOfMetadataTableOrThrow(DatabaseConfigurationCommon::kMetadataTable_Column_Name); |
263 |
2/4✓ Branch 3 taken 30 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 30 times.
✗ Branch 7 not taken.
|
30 | const auto column_name_ancestor_id = this->GetDocument()->GetDataBaseConfigurationCommon()->GetColumnNameOfMetadataTableOrThrow(DatabaseConfigurationCommon::kMetadataTable_Column_AncestorId); |
264 | |||
265 |
2/2✓ Branch 1 taken 20 times.
✓ Branch 2 taken 10 times.
|
30 | if (parent.has_value()) |
266 | { | ||
267 |
2/2✓ Branch 0 taken 16 times.
✓ Branch 1 taken 4 times.
|
20 | if (!recursively) |
268 | { | ||
269 | string_stream << "DELETE FROM [" << metadata_table_name << "] WHERE " << | ||
270 | "[" << column_name_pk << "]=?1 AND NOT EXISTS(" << | ||
271 |
13/26✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 16 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 16 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 16 times.
✗ Branch 14 not taken.
✓ Branch 16 taken 16 times.
✗ Branch 17 not taken.
✓ Branch 19 taken 16 times.
✗ Branch 20 not taken.
✓ Branch 22 taken 16 times.
✗ Branch 23 not taken.
✓ Branch 25 taken 16 times.
✗ Branch 26 not taken.
✓ Branch 28 taken 16 times.
✗ Branch 29 not taken.
✓ Branch 31 taken 16 times.
✗ Branch 32 not taken.
✓ Branch 34 taken 16 times.
✗ Branch 35 not taken.
✓ Branch 37 taken 16 times.
✗ Branch 38 not taken.
|
16 | "SELECT 1 FROM [" << metadata_table_name << "] WHERE " << "[" << column_name_ancestor_id << "]=?1" << ");"; |
272 | } | ||
273 | else | ||
274 | { | ||
275 | // To delete all items below a given node recursively we use a CTE (Common Table Expression) to get all child nodes | ||
276 | // of the given node and then delete all items with a PK that is in the result set of the CTE. | ||
277 | // The WITH RECURSIVE clause defines a recursive CTE called children that selects all child nodes of the given node | ||
278 | // recursively. The first SELECT statement selects all direct child nodes of the given node, while the second SELECT | ||
279 | // statement selects all child nodes of the child nodes recursively by joining with the children CTE. | ||
280 | // After the children CTE is defined, a separate DELETE statement is executed that deletes all rows in the hierarchy | ||
281 | // table that have an id that matches any of the id values returned by the children CTE, and we also delete the | ||
282 | // specified node itself. | ||
283 | string_stream << "WITH RECURSIVE children(id) AS (" << | ||
284 | "SELECT [" << column_name_pk << "] FROM [" << metadata_table_name << "] WHERE " << "[" << column_name_ancestor_id << "]=?1" << | ||
285 | "UNION ALL " << | ||
286 | "SELECT [" << metadata_table_name << "].[" << column_name_pk << "] FROM [" << metadata_table_name << "] JOIN children ON [" << metadata_table_name << "].[" << column_name_ancestor_id << "]=children.id" << | ||
287 | ") " << | ||
288 |
31/62✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 4 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 4 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 4 times.
✗ Branch 14 not taken.
✓ Branch 16 taken 4 times.
✗ Branch 17 not taken.
✓ Branch 19 taken 4 times.
✗ Branch 20 not taken.
✓ Branch 22 taken 4 times.
✗ Branch 23 not taken.
✓ Branch 25 taken 4 times.
✗ Branch 26 not taken.
✓ Branch 28 taken 4 times.
✗ Branch 29 not taken.
✓ Branch 31 taken 4 times.
✗ Branch 32 not taken.
✓ Branch 34 taken 4 times.
✗ Branch 35 not taken.
✓ Branch 37 taken 4 times.
✗ Branch 38 not taken.
✓ Branch 40 taken 4 times.
✗ Branch 41 not taken.
✓ Branch 43 taken 4 times.
✗ Branch 44 not taken.
✓ Branch 46 taken 4 times.
✗ Branch 47 not taken.
✓ Branch 49 taken 4 times.
✗ Branch 50 not taken.
✓ Branch 52 taken 4 times.
✗ Branch 53 not taken.
✓ Branch 55 taken 4 times.
✗ Branch 56 not taken.
✓ Branch 58 taken 4 times.
✗ Branch 59 not taken.
✓ Branch 61 taken 4 times.
✗ Branch 62 not taken.
✓ Branch 64 taken 4 times.
✗ Branch 65 not taken.
✓ Branch 67 taken 4 times.
✗ Branch 68 not taken.
✓ Branch 70 taken 4 times.
✗ Branch 71 not taken.
✓ Branch 73 taken 4 times.
✗ Branch 74 not taken.
✓ Branch 76 taken 4 times.
✗ Branch 77 not taken.
✓ Branch 79 taken 4 times.
✗ Branch 80 not taken.
✓ Branch 82 taken 4 times.
✗ Branch 83 not taken.
✓ Branch 85 taken 4 times.
✗ Branch 86 not taken.
✓ Branch 88 taken 4 times.
✗ Branch 89 not taken.
✓ Branch 91 taken 4 times.
✗ Branch 92 not taken.
|
4 | "DELETE FROM [" << metadata_table_name << "] WHERE " << "[" << "Pk" << "] IN (SELECT id FROM children) OR " << "[" << column_name_pk << "]=?1;"; |
289 | } | ||
290 | |||
291 | |||
292 |
2/4✓ Branch 5 taken 20 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 20 times.
✗ Branch 9 not taken.
|
20 | auto statement = this->GetDocument()->GetDatabase_connection()->PrepareStatement(string_stream.str()); |
293 |
2/4✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 20 times.
✗ Branch 6 not taken.
|
20 | statement->BindInt64(1, parent.value()); |
294 | 20 | return statement; | |
295 | 20 | } | |
296 | else | ||
297 | { | ||
298 | // This means that we want to delete the "root" node. It is not possible to delete the "root" itself, but everything in it. | ||
299 | // Following this logic, only "recursively=true" is allowed or makes sense. | ||
300 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 4 times.
|
10 | if (recursively) |
301 | { | ||
302 | string_stream << "WITH RECURSIVE children(id) AS (" << | ||
303 | "SELECT [" << column_name_pk << "] FROM [" << metadata_table_name << "] WHERE " << "[" << column_name_ancestor_id << "] IS NULL " << | ||
304 | "UNION ALL " << | ||
305 | "SELECT [" << metadata_table_name << "].[" << column_name_pk << "] FROM [" << metadata_table_name << "] JOIN children ON [" << metadata_table_name << "].[" << column_name_ancestor_id << "]=children.id" << | ||
306 | ") " << | ||
307 |
31/62✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 6 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 6 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 6 times.
✗ Branch 14 not taken.
✓ Branch 16 taken 6 times.
✗ Branch 17 not taken.
✓ Branch 19 taken 6 times.
✗ Branch 20 not taken.
✓ Branch 22 taken 6 times.
✗ Branch 23 not taken.
✓ Branch 25 taken 6 times.
✗ Branch 26 not taken.
✓ Branch 28 taken 6 times.
✗ Branch 29 not taken.
✓ Branch 31 taken 6 times.
✗ Branch 32 not taken.
✓ Branch 34 taken 6 times.
✗ Branch 35 not taken.
✓ Branch 37 taken 6 times.
✗ Branch 38 not taken.
✓ Branch 40 taken 6 times.
✗ Branch 41 not taken.
✓ Branch 43 taken 6 times.
✗ Branch 44 not taken.
✓ Branch 46 taken 6 times.
✗ Branch 47 not taken.
✓ Branch 49 taken 6 times.
✗ Branch 50 not taken.
✓ Branch 52 taken 6 times.
✗ Branch 53 not taken.
✓ Branch 55 taken 6 times.
✗ Branch 56 not taken.
✓ Branch 58 taken 6 times.
✗ Branch 59 not taken.
✓ Branch 61 taken 6 times.
✗ Branch 62 not taken.
✓ Branch 64 taken 6 times.
✗ Branch 65 not taken.
✓ Branch 67 taken 6 times.
✗ Branch 68 not taken.
✓ Branch 70 taken 6 times.
✗ Branch 71 not taken.
✓ Branch 73 taken 6 times.
✗ Branch 74 not taken.
✓ Branch 76 taken 6 times.
✗ Branch 77 not taken.
✓ Branch 79 taken 6 times.
✗ Branch 80 not taken.
✓ Branch 82 taken 6 times.
✗ Branch 83 not taken.
✓ Branch 85 taken 6 times.
✗ Branch 86 not taken.
✓ Branch 88 taken 6 times.
✗ Branch 89 not taken.
✓ Branch 91 taken 6 times.
✗ Branch 92 not taken.
|
6 | "DELETE FROM [" << metadata_table_name << "] WHERE " << "[" << column_name_pk << "] IN (SELECT id FROM children) OR " << "[" << column_name_ancestor_id << "] IS NULL;"; |
308 | |||
309 |
2/4✓ Branch 5 taken 6 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 6 times.
✗ Branch 9 not taken.
|
6 | return this->GetDocument()->GetDatabase_connection()->PrepareStatement(string_stream.str()); |
310 | } | ||
311 | else | ||
312 | { | ||
313 | 4 | return nullptr; | |
314 | } | ||
315 | } | ||
316 | 30 | } | |
317 | |||
318 | 634 | void DocumentMetadataWriter::CheckNodeNameAndThrowIfInvalid(const std::string& name) | |
319 | { | ||
320 | // the string must not be empty and it must not contain a slash | ||
321 | // TODO(JBL): is the check for '/' sufficient (for UTF8-strings)? | ||
322 |
6/6✓ Branch 1 taken 632 times.
✓ Branch 2 taken 2 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 630 times.
✓ Branch 6 taken 4 times.
✓ Branch 7 taken 630 times.
|
634 | if (name.empty() || name.find('/') != std::string::npos) |
323 | { | ||
324 |
1/2✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
4 | throw imgdoc2::invalid_argument_exception("The 'name' must not be empty and it must not contain a slash"); |
325 | } | ||
326 | 630 | } | |
327 |