SQLon test coverage


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 99.7% 367 / 0 / 368
Functions: 100.0% 57 / 0 / 57
Branches: 83.7% 297 / 0 / 355

src/query.cpp
Line Branch Exec Source
1 /******************************************************************************
2 **
3 ** Copyright (C) 2026 Ivan Pinezhaninov <ivan.pinezhaninov@gmail.com>
4 **
5 ** This file is part of the SQLon - which can be found at
6 ** https://github.com/IvanPinezhaninov/sqlon/.
7 **
8 ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
9 ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
10 ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
11 ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
12 ** DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
13 ** OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
14 ** THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15 **
16 ******************************************************************************/
17
18 #include <sqlon/query.h>
19
20 #include <sqlon/error.h>
21
22 #include <memory>
23 #include <utility>
24
25 namespace sqlon {
26 namespace detail {
27
28 namespace {
29
30 59 bool same_column(const detail::expression_ptr& left, const detail::expression_ptr& right)
31 {
32
2/4
✓ Branch 2 taken 59 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 59 times.
✗ Branch 6 not taken.
118 return left && right && left->kind == detail::expression_kind::column &&
33
5/8
✓ Branch 0 taken 59 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 59 times.
✗ Branch 4 not taken.
✓ Branch 8 taken 59 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 11 times.
✓ Branch 11 taken 48 times.
177 right->kind == detail::expression_kind::column && left->qualifier == right->qualifier &&
34 118 left->text == right->text;
35 }
36
37 149 std::vector<std::string> target_qualifier(const detail::query_node& query)
38 {
39
3/6
✓ Branch 1 taken 149 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 149 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 149 times.
149 if (!query.target || query.target->kind != detail::relation_kind::table) return {};
40
1/7
✗ Branch 2 not taken.
✓ Branch 3 taken 149 times.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
149 if (!query.target->alias.empty()) return {query.target->alias};
41
42
1/1
✓ Branch 2 taken 149 times.
149 std::vector<std::string> qualifier = query.target->qualifiers;
43
1/1
✓ Branch 2 taken 149 times.
149 qualifier.push_back(query.target->name);
44 149 return qualifier;
45 149 }
46
47 79 void require_unique_columns(const std::vector<detail::expression_ptr>& columns, const char* message)
48 {
49
2/2
✓ Branch 1 taken 113 times.
✓ Branch 2 taken 78 times.
191 for (std::size_t index = 0; index < columns.size(); ++index) {
50
2/2
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 112 times.
151 for (std::size_t previous = 0; previous < index; ++previous) {
51
3/3
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 38 times.
✓ Branch 7 taken 1 time.
39 if (same_column(columns[index], columns[previous])) throw invalid_query{message};
52 }
53 }
54 78 }
55
56 128 void require_target_columns(const detail::query_node& query, const std::vector<detail::expression_ptr>& columns,
57 const char* message)
58 {
59
1/1
✓ Branch 1 taken 128 times.
128 const std::vector<std::string> qualifier = target_qualifier(query);
60
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 128 times.
✗ Branch 5 not taken.
128 if (qualifier.empty()) throw invalid_query{message};
61
62
2/2
✓ Branch 5 taken 170 times.
✓ Branch 6 taken 122 times.
292 for (const detail::expression_ptr& column : columns) {
63
7/9
✓ Branch 1 taken 170 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 170 times.
✗ Branch 5 not taken.
✓ Branch 8 taken 170 times.
✓ Branch 10 taken 6 times.
✓ Branch 11 taken 164 times.
✓ Branch 12 taken 6 times.
✓ Branch 13 taken 164 times.
170 if (!column || column->kind != detail::expression_kind::column || column->qualifier != qualifier)
64
1/1
✓ Branch 2 taken 6 times.
6 throw invalid_query{message};
65 }
66 128 }
67
68 35 void require_unique_assignments(const std::vector<detail::assignment_node>& assignments, const char* message)
69 {
70
2/2
✓ Branch 1 taken 37 times.
✓ Branch 2 taken 34 times.
71 for (std::size_t index = 0; index < assignments.size(); ++index) {
71
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 36 times.
38 for (std::size_t previous = 0; previous < index; ++previous) {
72
3/3
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 1 time.
✓ Branch 7 taken 1 time.
2 if (same_column(assignments[index].column, assignments[previous].column)) throw invalid_query{message};
73 }
74 }
75 34 }
76
77 23 void require_inserted_columns(const detail::expression_ptr& expression, const std::vector<std::string>& qualifier,
78 const char* message)
79 {
80
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 23 times.
23 if (!expression) return;
81
6/6
✓ Branch 1 taken 13 times.
✓ Branch 2 taken 10 times.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 12 times.
✓ Branch 7 taken 1 time.
✓ Branch 8 taken 22 times.
23 if (expression->kind == detail::expression_kind::inserted_column && expression->qualifier != qualifier)
82
1/1
✓ Branch 2 taken 1 time.
1 throw invalid_query{message};
83
2/2
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 22 times.
24 for (const detail::expression_ptr& operand : expression->operands)
84
1/1
✓ Branch 1 taken 2 times.
2 require_inserted_columns(operand, qualifier, message);
85 }
86
87 } // namespace
88
89 9 void append_assignment_row(detail::query_node& query, const std::vector<detail::assignment_node>& assignments)
90 {
91
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
✗ Branch 5 not taken.
9 if (assignments.empty()) throw invalid_query{"an assignment-form INSERT row cannot be empty"};
92
3/7
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 9 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 9 times.
✗ Branch 9 not taken.
9 if (query.default_values || query.insert_source) throw invalid_query{"an INSERT source was already specified"};
93
94 9 std::vector<detail::expression_ptr> assignment_columns;
95
1/1
✓ Branch 2 taken 9 times.
9 assignment_columns.reserve(assignments.size());
96
2/2
✓ Branch 5 taken 15 times.
✓ Branch 6 taken 9 times.
24 for (const detail::assignment_node& assignment : assignments)
97
1/1
✓ Branch 1 taken 15 times.
15 assignment_columns.push_back(assignment.column);
98
1/1
✓ Branch 1 taken 8 times.
9 require_target_columns(query, assignment_columns, "INSERT assignments must target columns of the inserted table");
99
100
2/2
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 3 times.
8 if (query.columns.empty()) {
101
2/2
✓ Branch 5 taken 9 times.
✓ Branch 6 taken 4 times.
13 for (const detail::assignment_node& assignment : assignments) {
102
2/2
✓ Branch 5 taken 4 times.
✓ Branch 6 taken 8 times.
12 for (const detail::expression_ptr& column : query.columns) {
103
3/3
✓ Branch 1 taken 4 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 3 times.
4 if (same_column(column, assignment.column))
104
1/1
✓ Branch 2 taken 1 time.
1 throw invalid_query{"an assignment-form INSERT row cannot contain a column more than once"};
105 }
106
107
1/1
✓ Branch 1 taken 8 times.
8 query.columns.push_back(assignment.column);
108 }
109 } else {
110
2/2
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 2 times.
3 if (assignments.size() != query.columns.size())
111
1/1
✓ Branch 2 taken 1 time.
1 throw invalid_query{"assignment-form INSERT rows must contain the same columns"};
112
113
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 1 time.
4 for (std::size_t index = 0; index < assignments.size(); ++index) {
114
3/3
✓ Branch 3 taken 3 times.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 2 times.
3 if (!same_column(query.columns[index], assignments[index].column))
115
1/1
✓ Branch 2 taken 1 time.
1 throw invalid_query{"assignment-form INSERT columns must use the same order in every row"};
116 }
117 }
118
119 5 std::vector<detail::expression_ptr> row;
120
1/1
✓ Branch 2 taken 5 times.
5 row.reserve(assignments.size());
121
2/2
✓ Branch 5 taken 9 times.
✓ Branch 6 taken 5 times.
14 for (const detail::assignment_node& assignment : assignments)
122
1/1
✓ Branch 1 taken 9 times.
9 row.push_back(assignment.value);
123
1/1
✓ Branch 2 taken 5 times.
5 query.values.push_back(std::move(row));
124 9 }
125
126 48 void set_insert_columns(detail::query_node& query, const std::vector<detail::expression_ptr>& columns)
127 {
128
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 48 times.
✗ Branch 5 not taken.
48 if (!query.columns.empty()) throw invalid_query{"INSERT columns were already specified"};
129
3/3
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 47 times.
✓ Branch 4 taken 1 time.
48 if (query.default_values) throw invalid_query{"DEFAULT VALUES cannot have an INSERT column list"};
130 47 require_target_columns(query, columns, "INSERT columns must belong to the inserted table");
131 46 require_unique_columns(columns, "an INSERT column list cannot contain a column more than once");
132 45 query.columns = columns;
133 45 }
134
135 17 void set_update_assignments(detail::query_node& query, const std::vector<detail::assignment_node>& assignments)
136 {
137
3/3
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 16 times.
✓ Branch 5 taken 1 time.
17 if (!query.assignments.empty()) throw invalid_query{"SET was already specified"};
138
3/3
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 15 times.
✓ Branch 5 taken 1 time.
16 if (assignments.empty()) throw invalid_query{"SET requires at least one assignment"};
139 15 std::vector<detail::expression_ptr> assignment_columns;
140
1/1
✓ Branch 2 taken 15 times.
15 assignment_columns.reserve(assignments.size());
141
2/2
✓ Branch 5 taken 17 times.
✓ Branch 6 taken 15 times.
32 for (const detail::assignment_node& assignment : assignments)
142
1/1
✓ Branch 1 taken 17 times.
17 assignment_columns.push_back(assignment.column);
143
1/1
✓ Branch 1 taken 14 times.
15 require_target_columns(query, assignment_columns, "SET assignments must target columns of the updated table");
144
1/1
✓ Branch 1 taken 13 times.
14 require_unique_assignments(assignments, "SET cannot assign a column more than once");
145
1/1
✓ Branch 1 taken 13 times.
13 query.assignments = assignments;
146 15 }
147
148 25 void set_conflict_target(detail::query_node& query, const std::vector<detail::expression_ptr>& columns)
149 {
150
5/6
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 24 times.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 24 times.
25 if (query.conflict.action != detail::conflict_clause_node::kind::none || !query.conflict.target.empty())
151
1/1
✓ Branch 2 taken 1 time.
1 throw invalid_query{"ON CONFLICT was already specified"};
152 24 require_target_columns(query, columns, "ON CONFLICT columns must belong to the inserted table");
153 23 require_unique_columns(columns, "an ON CONFLICT target cannot contain a column more than once");
154 23 query.conflict.target = columns;
155 23 }
156
157 11 void set_conflict_do_nothing(detail::query_node& query, const std::vector<detail::expression_ptr>& columns)
158 {
159
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 10 times.
11 if (query.conflict.action != detail::conflict_clause_node::kind::none)
160
1/1
✓ Branch 2 taken 1 time.
1 throw invalid_query{"an INSERT conflict action was already specified"};
161
2/7
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 10 times.
✗ Branch 10 not taken.
10 if (!query.conflict.target.empty() && !columns.empty()) throw invalid_query{"ON CONFLICT was already specified"};
162 10 require_target_columns(query, columns, "ON CONFLICT columns must belong to the inserted table");
163 10 require_unique_columns(columns, "an ON CONFLICT target cannot contain a column more than once");
164
2/2
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 2 times.
10 if (!columns.empty()) query.conflict.target = columns;
165 10 query.conflict.action = detail::conflict_clause_node::kind::do_nothing;
166 10 }
167
168 28 void set_conflict_action(detail::query_node& query, detail::conflict_clause_node::kind action,
169 const std::vector<detail::assignment_node>& assignments)
170 {
171
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 26 times.
28 if (query.conflict.action != detail::conflict_clause_node::kind::none)
172
1/1
✓ Branch 2 taken 2 times.
2 throw invalid_query{"an INSERT conflict action was already specified"};
173
3/3
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 24 times.
✓ Branch 5 taken 2 times.
26 if (assignments.empty()) throw invalid_query{"an INSERT conflict update requires at least one assignment"};
174
6/6
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 17 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 6 times.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 23 times.
24 if (action == detail::conflict_clause_node::kind::duplicate_key_update && !query.conflict.target.empty())
175
1/1
✓ Branch 2 taken 1 time.
1 throw invalid_query{"ON DUPLICATE KEY UPDATE cannot use an ON CONFLICT target"};
176 23 std::vector<detail::expression_ptr> assignment_columns;
177
1/1
✓ Branch 2 taken 23 times.
23 assignment_columns.reserve(assignments.size());
178
2/2
✓ Branch 5 taken 23 times.
✓ Branch 6 taken 23 times.
46 for (const detail::assignment_node& assignment : assignments)
179
1/1
✓ Branch 1 taken 23 times.
23 assignment_columns.push_back(assignment.column);
180
1/1
✓ Branch 1 taken 21 times.
23 require_target_columns(query, assignment_columns,
181 "INSERT conflict assignments must target columns of the inserted table");
182
1/1
✓ Branch 1 taken 21 times.
21 require_unique_assignments(assignments, "an INSERT conflict update cannot assign a column more than once");
183
1/1
✓ Branch 1 taken 21 times.
21 const std::vector<std::string> qualifier = target_qualifier(query);
184
2/2
✓ Branch 5 taken 21 times.
✓ Branch 6 taken 20 times.
41 for (const detail::assignment_node& assignment : assignments) {
185
1/1
✓ Branch 1 taken 20 times.
21 require_inserted_columns(assignment.value, qualifier,
186 "inserted values must reference columns of the inserted table");
187 }
188 20 query.conflict.action = action;
189
1/1
✓ Branch 1 taken 20 times.
20 query.conflict.assignments = assignments;
190 24 }
191
192 } // namespace detail
193
194 683 query::query(detail::query_ptr node)
195 683 : m_node(std::move(node))
196 683 {}
197
198 306 const detail::query_ptr& detail::query_access::node(const query& value) noexcept
199 {
200 306 return value.m_node;
201 }
202
203 447 std::shared_ptr<detail::query_node> detail::query_access::copy(const query& value)
204 {
205 447 return std::make_shared<detail::query_node>(*value.m_node);
206 }
207
208 1 ordering ordering::nulls_first() const
209 {
210 1 ordering copy = *this;
211 1 detail::ordering_access::node(copy).nulls = detail::nulls_order::first;
212 1 return copy;
213 }
214
215 3 ordering ordering::nulls_last() const
216 {
217 3 ordering copy = *this;
218 3 detail::ordering_access::node(copy).nulls = detail::nulls_order::last;
219 3 return copy;
220 }
221
222 391 select_query::select_query(detail::query_ptr node)
223 391 : query(std::move(node))
224 391 {}
225
226 2 select_query select_query::distinct() const
227 {
228
1/1
✓ Branch 1 taken 2 times.
2 std::shared_ptr<detail::query_node> copy = detail::query_access::copy(*this);
229 2 copy->distinct = true;
230 4 return select_query{std::move(copy)};
231 2 }
232
233 36 select_query select_query::where(const expression<bool>& predicate) const
234 {
235
1/1
✓ Branch 1 taken 36 times.
36 std::shared_ptr<detail::query_node> copy = detail::query_access::copy(*this);
236
3/3
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 35 times.
✓ Branch 5 taken 1 time.
36 if (copy->where_set) throw invalid_query{"WHERE was already specified"};
237 35 copy->where = detail::expression_access::node(predicate);
238 35 copy->where_set = true;
239 70 return select_query{std::move(copy)};
240 36 }
241
242 5 select_query select_query::where(const conditions& predicates) const
243 {
244
1/1
✓ Branch 1 taken 5 times.
5 std::shared_ptr<detail::query_node> copy = detail::query_access::copy(*this);
245
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 5 not taken.
5 if (copy->where_set) throw invalid_query{"WHERE was already specified"};
246
1/1
✓ Branch 1 taken 5 times.
5 copy->where = detail::conditions_access::node(predicates);
247 5 copy->where_set = true;
248 10 return select_query{std::move(copy)};
249 5 }
250
251 6 select_query select_query::having(const expression<bool>& predicate) const
252 {
253
1/1
✓ Branch 1 taken 6 times.
6 std::shared_ptr<detail::query_node> copy = detail::query_access::copy(*this);
254
3/3
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 5 times.
✓ Branch 5 taken 1 time.
6 if (copy->having_set) throw invalid_query{"HAVING was already specified"};
255 5 copy->having = detail::expression_access::node(predicate);
256 5 copy->having_set = true;
257 10 return select_query{std::move(copy)};
258 6 }
259
260 12 select_query select_query::limit(std::uint64_t value) const
261 {
262
1/1
✓ Branch 1 taken 12 times.
12 std::shared_ptr<detail::query_node> copy = detail::query_access::copy(*this);
263
3/3
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 11 times.
✓ Branch 6 taken 1 time.
12 if (copy->limit) throw invalid_query{"LIMIT was already specified"};
264 11 copy->limit = value;
265 22 return select_query{std::move(copy)};
266 12 }
267
268 12 select_query select_query::offset(std::uint64_t value) const
269 {
270
1/1
✓ Branch 1 taken 12 times.
12 std::shared_ptr<detail::query_node> copy = detail::query_access::copy(*this);
271
3/3
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 11 times.
✓ Branch 6 taken 1 time.
12 if (copy->offset) throw invalid_query{"OFFSET was already specified"};
272 11 copy->offset = value;
273 22 return select_query{std::move(copy)};
274 12 }
275
276 5 select_query select_query::union_(const select_query& right) const
277 {
278 5 return add_set_operation(detail::set_operation_kind::union_, right);
279 }
280
281 6 select_query select_query::union_all(const select_query& right) const
282 {
283 6 return add_set_operation(detail::set_operation_kind::union_all, right);
284 }
285
286 4 select_query select_query::intersect(const select_query& right) const
287 {
288 4 return add_set_operation(detail::set_operation_kind::intersect, right);
289 }
290
291 3 select_query select_query::except(const select_query& right) const
292 {
293 3 return add_set_operation(detail::set_operation_kind::except, right);
294 }
295
296 18 select_query select_query::add_set_operation(detail::set_operation_kind kind, const select_query& right) const
297 {
298
1/1
✓ Branch 1 taken 18 times.
18 std::shared_ptr<detail::query_node> copy = detail::query_access::copy(*this);
299
1/1
✓ Branch 2 taken 18 times.
36 copy->set_operations.push_back({kind, detail::query_access::node(right)});
300 36 return select_query{std::move(copy)};
301 36 }
302
303 235 insert_query::insert_query(detail::query_ptr node)
304 235 : query(std::move(node))
305 235 {}
306
307 44 insert_query insert_query::append_row(std::vector<detail::expression_ptr> row) const
308 {
309
1/1
✓ Branch 1 taken 44 times.
44 std::shared_ptr<detail::query_node> copy = detail::query_access::copy(*this);
310
1/3
✗ Branch 2 not taken.
✓ Branch 3 taken 44 times.
✗ Branch 6 not taken.
44 if (copy->columns.empty()) throw invalid_query{"positional INSERT values require a column list"};
311
3/7
✓ Branch 1 taken 44 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 44 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 44 times.
✗ Branch 11 not taken.
44 if (copy->default_values || copy->insert_source) throw invalid_query{"an INSERT source was already specified"};
312
1/3
✗ Branch 3 not taken.
✓ Branch 4 taken 44 times.
✗ Branch 7 not taken.
44 if (row.size() != copy->columns.size()) throw invalid_query{"INSERT row width must match its column list"};
313
1/1
✓ Branch 3 taken 44 times.
44 copy->values.push_back(std::move(row));
314 88 return insert_query{std::move(copy)};
315 44 }
316
317 1 insert_query insert_query::values(std::initializer_list<assignment> values) const
318 {
319
1/1
✓ Branch 1 taken 1 time.
1 std::shared_ptr<detail::query_node> copy = detail::query_access::copy(*this);
320 1 std::vector<detail::assignment_node> assignments;
321
1/1
✓ Branch 2 taken 1 time.
1 assignments.reserve(values.size());
322
2/2
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1 time.
3 for (const assignment& value : values)
323
1/1
✓ Branch 2 taken 2 times.
2 assignments.push_back(detail::assignment_access::node(value));
324
1/1
✓ Branch 2 taken 1 time.
1 detail::append_assignment_row(*copy, assignments);
325 2 return insert_query{std::move(copy)};
326 1 }
327
328 9 insert_query insert_query::default_values() const
329 {
330
1/1
✓ Branch 1 taken 9 times.
9 std::shared_ptr<detail::query_node> copy = detail::query_access::copy(*this);
331
7/10
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 1 time.
✓ Branch 5 taken 8 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 8 times.
✗ Branch 10 not taken.
✗ Branch 13 not taken.
✓ Branch 14 taken 8 times.
✓ Branch 15 taken 1 time.
✓ Branch 16 taken 8 times.
9 if (copy->default_values || copy->insert_source || !copy->columns.empty() || !copy->values.empty())
332
1/1
✓ Branch 2 taken 1 time.
1 throw invalid_query{"an INSERT source was already specified"};
333 8 copy->default_values = true;
334 16 return insert_query{std::move(copy)};
335 9 }
336
337 9 insert_query insert_query::from_select(const select_query& source) const
338 {
339
1/1
✓ Branch 1 taken 9 times.
9 std::shared_ptr<detail::query_node> copy = detail::query_access::copy(*this);
340
3/3
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 8 times.
✓ Branch 6 taken 1 time.
9 if (copy->columns.empty()) throw invalid_query{"INSERT SELECT requires a column list"};
341
6/8
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 7 times.
✓ Branch 6 taken 1 time.
✗ Branch 9 not taken.
✓ Branch 10 taken 7 times.
✓ Branch 11 taken 1 time.
✓ Branch 12 taken 7 times.
8 if (copy->default_values || copy->insert_source || !copy->values.empty())
342
1/1
✓ Branch 2 taken 1 time.
1 throw invalid_query{"an INSERT source was already specified"};
343
1/3
✗ Branch 2 not taken.
✓ Branch 3 taken 7 times.
✗ Branch 6 not taken.
7 if (!detail::query_access::node(source)) throw invalid_query{"INSERT SELECT requires a source query"};
344
2/2
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 6 times.
7 if (detail::query_access::node(source)->projection.size() != copy->columns.size())
345
1/1
✓ Branch 2 taken 1 time.
1 throw invalid_query{"INSERT SELECT projection width must match its column list"};
346 6 copy->insert_source = detail::query_access::node(source);
347 12 return insert_query{std::move(copy)};
348 9 }
349
350 17 insert_query insert_query::on_conflict_update(std::initializer_list<assignment> assignments) const
351 {
352
1/1
✓ Branch 1 taken 17 times.
17 std::shared_ptr<detail::query_node> copy = detail::query_access::copy(*this);
353
2/2
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 15 times.
17 if (copy->conflict.target.empty())
354
1/1
✓ Branch 2 taken 2 times.
2 throw invalid_query{"ON CONFLICT DO UPDATE requires a target; use on_any_conflict_update for any conflict"};
355 15 std::vector<detail::assignment_node> values;
356
1/1
✓ Branch 2 taken 15 times.
15 values.reserve(assignments.size());
357
2/2
✓ Branch 2 taken 15 times.
✓ Branch 3 taken 15 times.
30 for (const assignment& value : assignments)
358
1/1
✓ Branch 2 taken 15 times.
15 values.push_back(detail::assignment_access::node(value));
359
1/1
✓ Branch 2 taken 12 times.
15 detail::set_conflict_action(*copy, detail::conflict_clause_node::kind::update, values);
360
361 24 return insert_query{std::move(copy)};
362 20 }
363
364 7 insert_query insert_query::on_conflict_update_inserted() const
365 {
366
1/1
✓ Branch 1 taken 7 times.
7 std::shared_ptr<detail::query_node> copy = detail::query_access::copy(*this);
367
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
7 if (copy->conflict.action != detail::conflict_clause_node::kind::none)
368 ✗ throw invalid_query{"an INSERT conflict action was already specified"};
369
3/3
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 6 times.
✓ Branch 6 taken 1 time.
7 if (copy->conflict.target.empty()) throw invalid_query{"updating proposed values requires a conflict target"};
370
1/3
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
✗ Branch 6 not taken.
6 if (copy->columns.empty()) throw invalid_query{"updating proposed values requires an INSERT column list"};
371
372 6 bool has_update_column = false;
373
2/2
✓ Branch 6 taken 11 times.
✓ Branch 7 taken 1 time.
12 for (const detail::expression_ptr& column : copy->columns) {
374 11 bool is_target = false;
375
2/2
✓ Branch 6 taken 11 times.
✓ Branch 7 taken 5 times.
16 for (const detail::expression_ptr& target : copy->conflict.target) {
376
3/3
✓ Branch 1 taken 11 times.
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 5 times.
11 if (detail::same_column(column, target)) {
377 6 is_target = true;
378 6 break;
379 }
380 }
381
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 6 times.
11 if (!is_target) {
382 5 has_update_column = true;
383 5 break;
384 }
385 }
386
3/3
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 5 times.
✓ Branch 4 taken 1 time.
6 if (!has_update_column) throw invalid_query{"updating proposed values requires at least one non-target column"};
387
388 5 copy->conflict.action = detail::conflict_clause_node::kind::update_inserted_values;
389 10 return insert_query{std::move(copy)};
390 7 }
391
392 6 insert_query insert_query::on_any_conflict_update(std::initializer_list<assignment> assignments) const
393 {
394
1/1
✓ Branch 1 taken 6 times.
6 std::shared_ptr<detail::query_node> copy = detail::query_access::copy(*this);
395
2/2
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 5 times.
6 if (!copy->conflict.target.empty())
396
1/1
✓ Branch 2 taken 1 time.
1 throw invalid_query{"on_any_conflict_update cannot be combined with an ON CONFLICT target"};
397 5 std::vector<detail::assignment_node> values;
398
1/1
✓ Branch 2 taken 5 times.
5 values.reserve(assignments.size());
399
2/2
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 5 times.
9 for (const assignment& value : assignments)
400
1/1
✓ Branch 2 taken 4 times.
4 values.push_back(detail::assignment_access::node(value));
401
1/1
✓ Branch 2 taken 2 times.
5 detail::set_conflict_action(*copy, detail::conflict_clause_node::kind::update, values);
402
403 4 return insert_query{std::move(copy)};
404 9 }
405
406 8 insert_query insert_query::on_duplicate_key_update(std::initializer_list<assignment> assignments) const
407 {
408
1/1
✓ Branch 1 taken 8 times.
8 std::shared_ptr<detail::query_node> copy = detail::query_access::copy(*this);
409 8 std::vector<detail::assignment_node> values;
410
1/1
✓ Branch 2 taken 8 times.
8 values.reserve(assignments.size());
411
2/2
✓ Branch 2 taken 7 times.
✓ Branch 3 taken 8 times.
15 for (const assignment& value : assignments)
412
1/1
✓ Branch 2 taken 7 times.
7 values.push_back(detail::assignment_access::node(value));
413
1/1
✓ Branch 2 taken 6 times.
8 detail::set_conflict_action(*copy, detail::conflict_clause_node::kind::duplicate_key_update, values);
414
415 12 return insert_query{std::move(copy)};
416 10 }
417
418 39 update_query::update_query(detail::query_ptr node)
419 39 : query(std::move(node))
420 39 {}
421
422 2 update_query update_query::set(std::initializer_list<assignment> assignments) const
423 {
424
1/1
✓ Branch 1 taken 2 times.
2 std::shared_ptr<detail::query_node> copy = detail::query_access::copy(*this);
425 2 std::vector<detail::assignment_node> values;
426
1/1
✓ Branch 2 taken 2 times.
2 values.reserve(assignments.size());
427
2/2
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 2 times.
3 for (const assignment& value : assignments)
428
1/1
✓ Branch 2 taken 1 time.
1 values.push_back(detail::assignment_access::node(value));
429
1/1
✓ Branch 2 taken 1 time.
2 detail::set_update_assignments(*copy, values);
430
431 2 return update_query{std::move(copy)};
432 3 }
433
434 6 update_query update_query::where(const expression<bool>& predicate) const
435 {
436
1/1
✓ Branch 1 taken 6 times.
6 std::shared_ptr<detail::query_node> copy = detail::query_access::copy(*this);
437
3/3
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 5 times.
✓ Branch 5 taken 1 time.
6 if (copy->where_set) throw invalid_query{"WHERE was already specified"};
438 5 copy->where = detail::expression_access::node(predicate);
439 5 copy->where_set = true;
440 10 return update_query{std::move(copy)};
441 6 }
442
443 1 update_query update_query::where(const conditions& predicates) const
444 {
445
1/1
✓ Branch 1 taken 1 time.
1 std::shared_ptr<detail::query_node> copy = detail::query_access::copy(*this);
446
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 5 not taken.
1 if (copy->where_set) throw invalid_query{"WHERE was already specified"};
447
1/1
✓ Branch 1 taken 1 time.
1 copy->where = detail::conditions_access::node(predicates);
448 1 copy->where_set = true;
449 2 return update_query{std::move(copy)};
450 1 }
451
452 18 delete_query::delete_query(detail::query_ptr node)
453 18 : query(std::move(node))
454 18 {}
455
456 6 delete_query delete_query::where(const expression<bool>& predicate) const
457 {
458
1/1
✓ Branch 1 taken 6 times.
6 std::shared_ptr<detail::query_node> copy = detail::query_access::copy(*this);
459
3/3
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 5 times.
✓ Branch 5 taken 1 time.
6 if (copy->where_set) throw invalid_query{"WHERE was already specified"};
460 5 copy->where = detail::expression_access::node(predicate);
461 5 copy->where_set = true;
462 10 return delete_query{std::move(copy)};
463 6 }
464
465 1 delete_query delete_query::where(const conditions& predicates) const
466 {
467
1/1
✓ Branch 1 taken 1 time.
1 std::shared_ptr<detail::query_node> copy = detail::query_access::copy(*this);
468
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 5 not taken.
1 if (copy->where_set) throw invalid_query{"WHERE was already specified"};
469
1/1
✓ Branch 1 taken 1 time.
1 copy->where = detail::conditions_access::node(predicates);
470 1 copy->where_set = true;
471 2 return delete_query{std::move(copy)};
472 1 }
473
474 12 common_table_expression::common_table_expression(detail::common_table_expression_node definition,
475 12 detail::relation_ptr relation_node)
476 12 : relation(std::move(relation_node))
477 12 , m_definition(std::move(definition))
478
2/2
✓ Branch 3 taken 12 times.
✓ Branch 8 taken 12 times.
24 , m_name(m_definition.name)
479 12 {}
480
481 2 table common_table_expression::proxy_table() const
482 {
483
2/2
✓ Branch 1 taken 2 times.
✓ Branch 4 taken 2 times.
2 return table{m_name};
484 }
485
486 14 common_table_expression cte(std::string name, const query& value, std::vector<std::string> columns)
487 {
488
3/3
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 13 times.
✓ Branch 5 taken 1 time.
14 if (name.empty()) throw invalid_query{"a CTE requires a non-empty name"};
489
2/2
✓ Branch 5 taken 8 times.
✓ Branch 6 taken 12 times.
20 for (const std::string& column : columns)
490
3/3
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 7 times.
✓ Branch 5 taken 1 time.
8 if (column.empty()) throw invalid_query{"a CTE column requires a non-empty name"};
491
492
1/1
✓ Branch 1 taken 12 times.
12 std::shared_ptr<detail::relation_node> relation_node = std::make_shared<detail::relation_node>();
493 12 relation_node->kind = detail::relation_kind::cte;
494
1/1
✓ Branch 2 taken 12 times.
12 relation_node->name = name;
495 12 detail::common_table_expression_node definition{std::move(name), std::move(columns),
496 12 detail::query_access::node(value)};
497
1/1
✓ Branch 5 taken 12 times.
24 return common_table_expression{std::move(definition), std::move(relation_node)};
498 12 }
499
500 3 subquery_relation::subquery_relation(const select_query& value, std::string alias)
501 2 : relation(make_node(value, alias))
502
2/2
✓ Branch 1 taken 2 times.
✓ Branch 4 taken 2 times.
5 , m_alias(std::move(alias))
503 2 {}
504
505 3 detail::relation_ptr subquery_relation::make_node(const select_query& value, const std::string& alias)
506 {
507
3/3
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 2 times.
✓ Branch 5 taken 1 time.
3 if (alias.empty()) throw invalid_query{"a derived relation requires a non-empty alias"};
508
1/1
✓ Branch 1 taken 2 times.
2 std::shared_ptr<detail::relation_node> node = std::make_shared<detail::relation_node>();
509 2 node->kind = detail::relation_kind::subquery;
510 2 node->subquery = detail::query_access::node(value);
511
1/1
✓ Branch 2 taken 2 times.
2 node->alias = alias;
512 4 return node;
513 2 }
514
515 2 table subquery_relation::proxy_table() const
516 {
517
3/3
✓ Branch 1 taken 2 times.
✓ Branch 4 taken 2 times.
✓ Branch 7 taken 2 times.
2 return table{m_alias, m_alias};
518 }
519
520 3 subquery_relation alias(const select_query& value, std::string alias_name)
521 {
522
1/1
✓ Branch 3 taken 2 times.
3 return subquery_relation{value, std::move(alias_name)};
523 }
524
525 2 cte_relation::cte_relation(std::string name)
526 1 : relation(make_node(name))
527
2/2
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
3 , m_name(std::move(name))
528 1 {}
529
530 2 detail::relation_ptr cte_relation::make_node(const std::string& name)
531 {
532
3/3
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✓ Branch 5 taken 1 time.
2 if (name.empty()) throw invalid_query{"a CTE reference requires a non-empty name"};
533
1/1
✓ Branch 1 taken 1 time.
1 std::shared_ptr<detail::relation_node> node = std::make_shared<detail::relation_node>();
534 1 node->kind = detail::relation_kind::cte;
535
1/1
✓ Branch 2 taken 1 time.
1 node->name = name;
536 2 return node;
537 1 }
538
539 1 table cte_relation::proxy_table() const
540 {
541
2/2
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
1 return table{m_name};
542 }
543
544 2 cte_relation cte_reference(std::string name)
545 {
546
1/1
✓ Branch 3 taken 1 time.
2 return cte_relation{std::move(name)};
547 }
548
549 2 expression<bool> exists(const select_query& value)
550 {
551
1/1
✓ Branch 1 taken 2 times.
2 std::shared_ptr<detail::expression_node> node = std::make_shared<detail::expression_node>();
552 2 node->kind = detail::expression_kind::exists;
553 2 node->subquery = detail::query_access::node(value);
554
1/1
✓ Branch 3 taken 2 times.
4 return detail::expression_access::make<bool>(std::move(node));
555 2 }
556
557 } // namespace sqlon
558