SQLon test coverage


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 96.6% 1049 / 0 / 1086
Functions: 100.0% 82 / 0 / 82
Branches: 83.9% 1492 / 0 / 1778

src/render.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/error.h>
19 #include <sqlon/prepared_query.h>
20 #include <sqlon/render.h>
21
22 #include <cctype>
23 #include <cmath>
24 #include <cstdint>
25 #include <iomanip>
26 #include <limits>
27 #include <locale>
28 #include <optional>
29 #include <sstream>
30 #include <string>
31 #include <type_traits>
32 #include <unordered_map>
33 #include <unordered_set>
34 #include <utility>
35 #include <vector>
36
37 namespace sqlon {
38 namespace {
39
40 enum class renderer_mode : std::uint8_t { immediate, prepared };
41
42 enum class expression_context : std::uint8_t { value, result };
43
44 class renderer final {
45 public:
46 246 renderer(const sql_options& options, renderer_mode mode)
47 246 : m_options(options)
48 246 , m_mode(mode)
49 246 {}
50
51 224 rendered_query render_query(const query& value)
52 {
53
1/3
✗ Branch 0 not taken.
✓ Branch 1 taken 224 times.
✗ Branch 4 not taken.
224 if (m_mode != renderer_mode::immediate) throw render_error{"renderer is not in immediate mode"};
54 224 const detail::query_ptr& node = detail::query_access::node(value);
55
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 224 times.
✗ Branch 5 not taken.
224 if (!node) throw render_error{"cannot render an empty query node"};
56 224 collect_parameter_names(*node);
57
1/1
✓ Branch 2 taken 158 times.
224 m_result.sql = render_query_node(*node);
58 158 return std::move(m_result);
59 }
60
61 22 prepared_query render_prepared_query(const query& value)
62 {
63
1/3
✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
✗ Branch 4 not taken.
22 if (m_mode != renderer_mode::prepared) throw render_error{"renderer is not in prepared mode"};
64 22 const detail::query_ptr& node = detail::query_access::node(value);
65
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
✗ Branch 5 not taken.
22 if (!node) throw render_error{"cannot prepare an empty query node"};
66
1/1
✓ Branch 2 taken 22 times.
22 collect_parameter_names(*node);
67
1/1
✓ Branch 2 taken 20 times.
22 std::string sql = render_query_node(*node);
68
1/1
✓ Branch 7 taken 20 times.
40 return prepared_query{std::move(sql), std::move(m_prepared_parameters), std::move(m_prepared_bindings)};
69 20 }
70
71 private:
72 285 std::string render_query_node(const detail::query_node& value)
73 {
74 285 std::string sql = render_ctes(value);
75
4/5
✓ Branch 0 taken 196 times.
✓ Branch 1 taken 67 times.
✓ Branch 2 taken 12 times.
✓ Branch 3 taken 6 times.
✗ Branch 4 not taken.
281 switch (value.kind) {
76 196 case detail::statement_kind::select:
77
2/2
✓ Branch 1 taken 159 times.
✓ Branch 4 taken 159 times.
196 sql += render_select(value);
78 159 return sql;
79 67 case detail::statement_kind::insert:
80
2/2
✓ Branch 1 taken 42 times.
✓ Branch 4 taken 42 times.
67 sql += render_insert(value);
81 42 return sql;
82 12 case detail::statement_kind::update:
83
2/2
✓ Branch 1 taken 10 times.
✓ Branch 4 taken 10 times.
12 sql += render_update(value);
84 10 return sql;
85 6 case detail::statement_kind::delete_:
86
2/2
✓ Branch 1 taken 5 times.
✓ Branch 4 taken 5 times.
6 sql += render_delete(value);
87 5 return sql;
88 }
89 ✗ throw render_error{"unknown statement kind"};
90 65 }
91
92 196 std::string render_select(const detail::query_node& value)
93 {
94
3/3
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 195 times.
✓ Branch 5 taken 1 time.
196 if (value.projection.empty()) throw render_error{"SELECT requires at least one projection expression"};
95
96
3/3
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 191 times.
✓ Branch 3 taken 195 times.
195 std::string sql = value.distinct ? "SELECT DISTINCT " : "SELECT ";
97
7/8
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 189 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 4 times.
✓ Branch 6 taken 1 time.
✓ Branch 7 taken 1 time.
✓ Branch 8 taken 1 time.
✗ Branch 9 not taken.
196 const bool uses_top = m_options.row_limits == row_limit_style::top_offset_fetch && value.limit && !value.offset &&
98 1 value.set_operations.empty();
99
6/6
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 194 times.
✓ Branch 4 taken 1 time.
✓ Branch 7 taken 1 time.
✓ Branch 10 taken 1 time.
✓ Branch 13 taken 1 time.
195 if (uses_top) sql += "TOP (" + std::to_string(*value.limit) + ") ";
100
2/2
✓ Branch 1 taken 264 times.
✓ Branch 2 taken 171 times.
435 for (std::size_t index = 0; index < value.projection.size(); ++index) {
101
3/3
✓ Branch 0 taken 69 times.
✓ Branch 1 taken 195 times.
✓ Branch 3 taken 69 times.
264 if (index != 0) sql += ", ";
102
2/2
✓ Branch 2 taken 240 times.
✓ Branch 5 taken 240 times.
264 sql += render_expression(value.projection[index], 0, expression_context::result);
103 }
104
105
2/2
✓ Branch 1 taken 91 times.
✓ Branch 2 taken 80 times.
171 if (value.from)
106
3/3
✓ Branch 1 taken 89 times.
✓ Branch 4 taken 89 times.
✓ Branch 7 taken 89 times.
91 sql += " FROM " + render_relation(value.from);
107 else
108
2/2
✓ Branch 1 taken 78 times.
✓ Branch 4 taken 78 times.
80 sql += render_implicit_select_source();
109
5/5
✓ Branch 1 taken 46 times.
✓ Branch 2 taken 121 times.
✓ Branch 4 taken 44 times.
✓ Branch 7 taken 44 times.
✓ Branch 10 taken 44 times.
167 if (value.where) sql += " WHERE " + render_expression(value.where);
110
5/5
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 161 times.
✓ Branch 4 taken 4 times.
✓ Branch 7 taken 4 times.
✓ Branch 10 taken 4 times.
165 if (!value.group_by.empty()) sql += " GROUP BY " + render_expression_list(value.group_by);
111
5/5
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 161 times.
✓ Branch 4 taken 4 times.
✓ Branch 7 taken 4 times.
✓ Branch 10 taken 4 times.
165 if (value.having) sql += " HAVING " + render_expression(value.having);
112 165 std::optional<int> compound_precedence;
113
2/2
✓ Branch 4 taken 18 times.
✓ Branch 5 taken 163 times.
181 for (const detail::set_operation_node& operation : value.set_operations) {
114
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✗ Branch 5 not taken.
18 if (!operation.query) throw render_error{"a set operation requires a query"};
115 18 const int operation_precedence = set_operation_precedence(operation.kind);
116
7/7
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 13 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 3 times.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 16 times.
✓ Branch 11 taken 2 times.
18 if (compound_precedence && operation_precedence > *compound_precedence) sql = group_set_sql(std::move(sql));
117
4/4
✓ Branch 1 taken 16 times.
✓ Branch 4 taken 16 times.
✓ Branch 7 taken 16 times.
✓ Branch 10 taken 16 times.
18 sql += " " + set_operation_keyword(operation.kind) + " ";
118
2/2
✓ Branch 2 taken 16 times.
✓ Branch 5 taken 16 times.
16 sql += render_set_operand(*operation.query);
119 16 compound_precedence = operation_precedence;
120 }
121
122
5/5
✓ Branch 1 taken 15 times.
✓ Branch 2 taken 148 times.
✓ Branch 4 taken 14 times.
✓ Branch 7 taken 14 times.
✓ Branch 10 taken 14 times.
163 if (!value.order_by.empty()) sql += " ORDER BY " + render_order_by(value.order_by);
123
2/2
✓ Branch 1 taken 159 times.
✓ Branch 4 taken 159 times.
162 sql += render_row_limit(value, uses_top);
124 318 return sql;
125 36 }
126
127 84 std::string render_implicit_select_source() const
128 {
129
2/2
✓ Branch 1 taken 70 times.
✓ Branch 2 taken 14 times.
84 if (!m_options.implicit_select_source) return {};
130
131
1/1
✓ Branch 2 taken 14 times.
14 std::vector<std::string> qualifiers = m_options.implicit_select_source->identifier_parts;
132
3/3
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 13 times.
✓ Branch 5 taken 1 time.
14 if (qualifiers.empty()) throw render_error{"an implicit SELECT source requires an identifier"};
133 13 const std::string identifier = std::move(qualifiers.back());
134 13 qualifiers.pop_back();
135
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 12 times.
13 if (m_options.implicit_select_source->quote_identifier)
136
2/2
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
1 return " FROM " + render_qualified_identifier(qualifiers, identifier);
137
138 12 std::string source;
139
2/2
✓ Branch 4 taken 1 time.
✓ Branch 5 taken 12 times.
13 for (const std::string& qualifier : qualifiers) {
140
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 4 not taken.
1 if (!source.empty()) source += ".";
141
2/2
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
1 source += render_unquoted_identifier(qualifier);
142 }
143
3/3
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 11 times.
✓ Branch 4 taken 1 time.
12 if (!source.empty()) source += ".";
144
3/3
✓ Branch 1 taken 11 times.
✓ Branch 4 taken 11 times.
✓ Branch 7 taken 11 times.
12 return " FROM " + source + render_unquoted_identifier(identifier);
145 16 }
146
147 162 std::string render_row_limit(const detail::query_node& value, bool uses_top) const
148 {
149
6/6
✓ Branch 1 taken 152 times.
✓ Branch 2 taken 10 times.
✓ Branch 4 taken 144 times.
✓ Branch 5 taken 8 times.
✓ Branch 6 taken 144 times.
✓ Branch 7 taken 18 times.
162 if (!value.limit && !value.offset) return {};
150
151
4/4
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1 time.
18 switch (m_options.row_limits) {
152 12 case row_limit_style::limit_offset: {
153 12 std::string sql;
154
5/5
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 7 times.
✓ Branch 5 taken 5 times.
✓ Branch 8 taken 5 times.
✓ Branch 11 taken 5 times.
12 if (value.limit) sql += " LIMIT " + std::to_string(*value.limit);
155
7/8
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 5 times.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 7 times.
✓ Branch 7 taken 5 times.
✓ Branch 9 taken 6 times.
✓ Branch 12 taken 6 times.
12 if (!value.limit && value.offset) sql += offset_without_limit_prefix();
156
5/5
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 3 times.
✓ Branch 5 taken 8 times.
✓ Branch 8 taken 8 times.
✓ Branch 11 taken 8 times.
11 if (value.offset) sql += " OFFSET " + std::to_string(*value.offset);
157 11 return sql;
158 12 }
159 2 case row_limit_style::offset_fetch: {
160 2 std::string sql;
161
6/6
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✓ Branch 5 taken 1 time.
✓ Branch 8 taken 1 time.
✓ Branch 11 taken 1 time.
✓ Branch 14 taken 1 time.
2 if (value.offset) sql += " OFFSET " + std::to_string(*value.offset) + " ROWS";
162
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 if (value.limit) {
163
3/3
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✓ Branch 4 taken 2 times.
2 sql += value.offset ? " FETCH NEXT " : " FETCH FIRST ";
164
3/3
✓ Branch 2 taken 2 times.
✓ Branch 5 taken 2 times.
✓ Branch 8 taken 2 times.
2 sql += std::to_string(*value.limit) + " ROWS ONLY";
165 }
166 2 return sql;
167 2 }
168 3 case row_limit_style::top_offset_fetch:
169
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 2 times.
3 if (uses_top) return {};
170
3/3
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✓ Branch 5 taken 1 time.
2 if (value.order_by.empty()) throw render_error{"OFFSET/FETCH requires ORDER BY for the selected preset"};
171
3/3
✓ Branch 2 taken 1 time.
✓ Branch 5 taken 1 time.
✓ Branch 8 taken 1 time.
2 return " OFFSET " + std::to_string(value.offset.value_or(0)) + " ROWS" +
172
7/14
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
✓ Branch 5 taken 1 time.
✓ Branch 8 taken 1 time.
✓ Branch 11 taken 1 time.
✓ Branch 15 taken 1 time.
✓ Branch 18 taken 1 time.
✗ Branch 19 not taken.
✓ Branch 21 taken 1 time.
✗ Branch 22 not taken.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
3 (value.limit ? " FETCH NEXT " + std::to_string(*value.limit) + " ROWS ONLY" : std::string{});
173 }
174
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"unknown SELECT row-limiting style"};
175 }
176
177 7 std::string offset_without_limit_prefix() const
178 {
179
4/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 1 time.
7 switch (m_options.offset_without_limit) {
180 4 case offset_without_limit_style::standalone:
181 4 return {};
182 1 case offset_without_limit_style::negative_one_limit:
183
1/1
✓ Branch 1 taken 1 time.
2 return " LIMIT -1";
184 1 case offset_without_limit_style::maximum_unsigned_limit:
185
1/1
✓ Branch 1 taken 1 time.
2 return " LIMIT 18446744073709551615";
186 }
187
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"unknown OFFSET without LIMIT rendering style"};
188 }
189
190 67 std::string render_insert(const detail::query_node& value)
191 {
192
2/2
✓ Branch 0 taken 45 times.
✓ Branch 1 taken 22 times.
67 if (value.conflict.action != detail::conflict_clause_node::kind::none &&
193
2/2
✓ Branch 0 taken 38 times.
✓ Branch 1 taken 7 times.
45 value.conflict.action != detail::conflict_clause_node::kind::duplicate_key_update &&
194
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 28 times.
38 m_options.conflict_updates == conflict_update_style::merge_statement) {
195
1/1
✓ Branch 1 taken 6 times.
10 return render_merge_insert(value);
196 }
197
2/2
✓ Branch 0 taken 35 times.
✓ Branch 1 taken 22 times.
57 if (value.conflict.action != detail::conflict_clause_node::kind::none &&
198
2/2
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 7 times.
35 value.conflict.action != detail::conflict_clause_node::kind::duplicate_key_update &&
199
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 20 times.
28 m_options.conflict_updates == conflict_update_style::update_or_insert) {
200
1/1
✓ Branch 1 taken 4 times.
8 return render_update_or_insert(value);
201 }
202
203
1/1
✓ Branch 1 taken 49 times.
49 const bool uses_inserted_row = conflict_uses_inserted_row(value.conflict);
204 49 std::string inserted_row_alias;
205
4/4
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 36 times.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 8 times.
49 if (uses_inserted_row && m_options.inserted_row_references == inserted_row_reference_style::values_row_alias) {
206
5/6
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 4 times.
5 if (value.default_values || value.insert_source)
207
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"the selected proposed-row reference style requires an INSERT VALUES source"};
208
1/1
✓ Branch 1 taken 4 times.
4 inserted_row_alias = make_inserted_row_alias(value.target);
209 }
210
211
1/1
✓ Branch 1 taken 48 times.
48 std::string sql{"INSERT INTO "};
212
2/2
✓ Branch 1 taken 48 times.
✓ Branch 4 taken 48 times.
48 sql += render_mutation_target(value.target);
213
214
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 40 times.
48 if (value.default_values) {
215
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 7 times.
8 if (!has_capability(m_options.capabilities, sql_capability::default_values))
216
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"DEFAULT VALUES is not supported by the selected preset"};
217
4/8
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 7 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 7 times.
7 if (!value.columns.empty() || !value.values.empty() || value.insert_source)
218 ✗ throw render_error{"DEFAULT VALUES cannot be combined with another INSERT source"};
219
4/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 6 times.
10 if (value.conflict.action != detail::conflict_clause_node::kind::none &&
220
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 2 times.
3 !has_capability(m_options.capabilities, sql_capability::default_values_with_conflict)) {
221
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"DEFAULT VALUES cannot be combined with a conflict action for the selected preset"};
222 }
223
3/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
6 switch (m_options.default_values) {
224 3 case default_values_style::standard:
225
1/1
✓ Branch 1 taken 3 times.
3 sql += " DEFAULT VALUES";
226 3 break;
227 2 case default_values_style::empty_parenthesized_values:
228
1/1
✓ Branch 1 taken 2 times.
2 sql += " () VALUES ()";
229 2 break;
230 1 case default_values_style::unsupported:
231
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"the selected DEFAULT VALUES capability has no rendering style"};
232 }
233 } else {
234
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 40 times.
✗ Branch 5 not taken.
40 if (value.columns.empty()) throw render_error{"INSERT requires at least one column"};
235
4/4
✓ Branch 1 taken 40 times.
✓ Branch 4 taken 40 times.
✓ Branch 7 taken 40 times.
✓ Branch 10 taken 40 times.
40 sql += " (" + render_column_name_list(value.columns) + ") ";
236
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 36 times.
40 if (value.insert_source) {
237
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 3 times.
4 if (!has_capability(m_options.capabilities, sql_capability::insert_select))
238
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"INSERT SELECT is not supported by the selected preset"};
239
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 5 not taken.
3 if (!value.values.empty()) throw render_error{"INSERT SELECT cannot be combined with VALUES rows"};
240
7/8
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 1 time.
✗ Branch 7 not taken.
✓ Branch 8 taken 1 time.
✓ Branch 9 taken 2 times.
4 if (value.conflict.action != detail::conflict_clause_node::kind::none && !value.insert_source->where &&
241 1 has_capability(m_options.capabilities, sql_capability::insert_select_conflict_requires_where)) {
242 throw render_error{"INSERT SELECT with a conflict action requires a source WHERE clause for the selected "
243
1/1
✓ Branch 2 taken 1 time.
1 "preset"};
244 }
245
2/2
✓ Branch 2 taken 2 times.
✓ Branch 5 taken 2 times.
2 sql += render_query_node(*value.insert_source);
246 } else {
247
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 36 times.
✗ Branch 5 not taken.
36 if (value.values.empty()) throw render_error{"INSERT requires at least one VALUES row"};
248
6/6
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 28 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 6 times.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 34 times.
36 if (value.values.size() > 1 && !has_capability(m_options.capabilities, sql_capability::multi_row_values)) {
249
1/1
✓ Branch 2 taken 2 times.
2 throw render_error{"multiple INSERT VALUES rows are not supported by the selected preset"};
250 }
251
1/1
✓ Branch 1 taken 34 times.
34 sql += "VALUES ";
252
2/2
✓ Branch 1 taken 40 times.
✓ Branch 2 taken 33 times.
73 for (std::size_t row_index = 0; row_index < value.values.size(); ++row_index) {
253 40 const std::vector<detail::expression_ptr>& row = value.values[row_index];
254
1/3
✗ Branch 2 not taken.
✓ Branch 3 taken 40 times.
✗ Branch 6 not taken.
40 if (row.size() != value.columns.size()) throw render_error{"INSERT row width does not match its column list"};
255
3/3
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 34 times.
✓ Branch 3 taken 6 times.
40 if (row_index != 0) sql += ", ";
256
4/4
✓ Branch 1 taken 39 times.
✓ Branch 4 taken 39 times.
✓ Branch 7 taken 39 times.
✓ Branch 10 taken 39 times.
40 sql += "(" + render_value_expression_list(row) + ")";
257 }
258
5/5
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 29 times.
✓ Branch 4 taken 4 times.
✓ Branch 7 taken 4 times.
✓ Branch 10 taken 4 times.
33 if (!inserted_row_alias.empty()) sql += " AS " + quote_identifier(inserted_row_alias);
259 }
260 }
261
3/3
✓ Branch 1 taken 40 times.
✓ Branch 4 taken 33 times.
✓ Branch 7 taken 33 times.
47 sql += render_conflict_clause(value, inserted_row_alias, relation_qualifier(value.target));
262
2/2
✓ Branch 1 taken 32 times.
✓ Branch 4 taken 32 times.
33 sql += render_returning(value.returning);
263 32 return sql;
264 65 }
265
266 10 std::string render_merge_insert(const detail::query_node& value)
267 {
268
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
10 if (!has_capability(m_options.capabilities, sql_capability::merge))
269 ✗ throw render_error{"MERGE is not supported by the selected preset"};
270
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 9 times.
10 if (value.conflict.target.empty())
271
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"a MERGE conflict action requires an explicit conflict target"};
272
5/6
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 8 times.
9 if (value.default_values || value.insert_source)
273
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"generated MERGE statements require an INSERT VALUES source"};
274
3/6
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 8 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 8 times.
8 if (value.columns.empty() || value.values.empty())
275 ✗ throw render_error{"generated MERGE statements require INSERT columns and values"};
276
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 5 not taken.
8 if (!value.returning.empty()) throw render_error{"RETURNING is not supported by generated MERGE statements"};
277
278
2/2
✓ Branch 5 taken 8 times.
✓ Branch 6 taken 7 times.
15 for (const detail::expression_ptr& target : value.conflict.target) {
279
3/3
✓ Branch 1 taken 8 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 7 times.
8 if (!contains_column(value.columns, target))
280
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"MERGE conflict targets must be present in the INSERT column list"};
281 }
282
2/2
✓ Branch 5 taken 8 times.
✓ Branch 6 taken 7 times.
15 for (const std::vector<detail::expression_ptr>& row : value.values) {
283
1/3
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
✗ Branch 6 not taken.
8 if (row.size() != value.columns.size()) throw render_error{"INSERT row width does not match its column list"};
284
2/2
✓ Branch 5 taken 18 times.
✓ Branch 6 taken 8 times.
26 for (const detail::expression_ptr& expression : row)
285
2/3
✓ Branch 1 taken 18 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 18 times.
18 if (is_default_value(expression))
286 ✗ throw render_error{"DEFAULT values are not supported in generated MERGE sources"};
287 }
288
289
1/1
✓ Branch 1 taken 7 times.
14 const std::string target_alias{"_sqlon_target"};
290
1/1
✓ Branch 1 taken 7 times.
14 const std::string source_alias{"_sqlon_source"};
291
1/1
✓ Branch 1 taken 7 times.
7 std::string sql{"MERGE INTO "};
292
2/2
✓ Branch 1 taken 7 times.
✓ Branch 4 taken 7 times.
7 sql += render_mutation_target(value.target);
293
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 3 times.
7 if (m_options.merge_target_hint) {
294
3/3
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 3 times.
✓ Branch 6 taken 1 time.
4 if (m_options.merge_target_hint->empty()) throw render_error{"a MERGE target hint cannot be empty"};
295
296
2/2
✓ Branch 2 taken 3 times.
✓ Branch 5 taken 3 times.
3 sql += " " + *m_options.merge_target_hint;
297 }
298
2/2
✓ Branch 1 taken 6 times.
✓ Branch 4 taken 6 times.
6 sql += render_relation_alias(target_alias);
299
1/1
✓ Branch 1 taken 6 times.
6 sql += " USING ";
300
2/2
✓ Branch 1 taken 6 times.
✓ Branch 4 taken 6 times.
6 sql += render_merge_source(value, source_alias);
301
1/1
✓ Branch 1 taken 6 times.
6 sql += " ON (";
302
2/2
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 6 times.
12 for (std::size_t index = 0; index < value.conflict.target.size(); ++index) {
303
1/3
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
✗ Branch 3 not taken.
6 if (index != 0) sql += " AND ";
304
1/1
✓ Branch 2 taken 6 times.
6 const std::string column = render_column_name(value.conflict.target[index]);
305
4/4
✓ Branch 1 taken 6 times.
✓ Branch 4 taken 6 times.
✓ Branch 7 taken 6 times.
✓ Branch 10 taken 6 times.
6 sql += quote_identifier(target_alias) + "." + column;
306
1/1
✓ Branch 1 taken 6 times.
6 sql += " = ";
307
4/4
✓ Branch 1 taken 6 times.
✓ Branch 4 taken 6 times.
✓ Branch 7 taken 6 times.
✓ Branch 10 taken 6 times.
6 sql += quote_identifier(source_alias) + "." + column;
308 6 }
309
1/1
✓ Branch 1 taken 6 times.
6 sql += ")";
310
311
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
6 if (value.conflict.action == detail::conflict_clause_node::kind::update ||
312
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
4 value.conflict.action == detail::conflict_clause_node::kind::update_inserted_values) {
313
1/1
✓ Branch 1 taken 5 times.
5 sql += " WHEN MATCHED THEN UPDATE SET ";
314
5/5
✓ Branch 1 taken 5 times.
✓ Branch 4 taken 5 times.
✓ Branch 7 taken 5 times.
✓ Branch 10 taken 5 times.
✓ Branch 13 taken 5 times.
10 sql += render_assignments(conflict_assignments(value), true, source_alias, relation_qualifier(value.target),
315
1/1
✓ Branch 1 taken 5 times.
5 target_alias);
316
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 } else if (value.conflict.action != detail::conflict_clause_node::kind::do_nothing) {
317 ✗ throw render_error{"unknown MERGE conflict action"};
318 }
319
320
4/4
✓ Branch 1 taken 6 times.
✓ Branch 4 taken 6 times.
✓ Branch 7 taken 6 times.
✓ Branch 10 taken 6 times.
6 sql += " WHEN NOT MATCHED THEN INSERT (" + render_column_name_list(value.columns) + ") VALUES (";
321
2/2
✓ Branch 1 taken 14 times.
✓ Branch 2 taken 6 times.
20 for (std::size_t index = 0; index < value.columns.size(); ++index) {
322
3/3
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 6 times.
✓ Branch 3 taken 8 times.
14 if (index != 0) sql += ", ";
323
5/5
✓ Branch 2 taken 14 times.
✓ Branch 5 taken 14 times.
✓ Branch 8 taken 14 times.
✓ Branch 11 taken 14 times.
✓ Branch 14 taken 14 times.
14 sql += quote_identifier(source_alias) + "." + render_column_name(value.columns[index]);
324 }
325
1/1
✓ Branch 1 taken 6 times.
6 sql += ")";
326
3/3
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
✓ Branch 3 taken 3 times.
6 if (m_options.terminate_merge) sql += ";";
327 12 return sql;
328 9 }
329
330 6 std::string render_merge_source(const detail::query_node& value, const std::string& source_alias)
331 {
332
2/3
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
6 switch (m_options.merge_sources) {
333 3 case merge_source_style::values_table: {
334
1/1
✓ Branch 1 taken 3 times.
3 std::string sql{"(VALUES "};
335
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 3 times.
6 for (std::size_t row_index = 0; row_index < value.values.size(); ++row_index) {
336
1/3
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
✗ Branch 3 not taken.
3 if (row_index != 0) sql += ", ";
337
4/4
✓ Branch 2 taken 3 times.
✓ Branch 5 taken 3 times.
✓ Branch 8 taken 3 times.
✓ Branch 11 taken 3 times.
3 sql += "(" + render_value_expression_list(value.values[row_index]) + ")";
338 }
339
4/4
✓ Branch 1 taken 3 times.
✓ Branch 4 taken 3 times.
✓ Branch 7 taken 3 times.
✓ Branch 10 taken 3 times.
3 sql += ")" + render_relation_alias(source_alias) + " (";
340
3/3
✓ Branch 1 taken 3 times.
✓ Branch 4 taken 3 times.
✓ Branch 7 taken 3 times.
3 sql += render_column_name_list(value.columns) + ")";
341 3 return sql;
342 3 }
343 3 case merge_source_style::union_all_select: {
344
1/1
✓ Branch 1 taken 3 times.
3 std::string sql{"("};
345
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 3 times.
7 for (std::size_t row_index = 0; row_index < value.values.size(); ++row_index) {
346
3/3
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 3 times.
✓ Branch 3 taken 1 time.
4 if (row_index != 0) sql += " UNION ALL ";
347
1/1
✓ Branch 1 taken 4 times.
4 sql += "SELECT ";
348 4 const std::vector<detail::expression_ptr>& row = value.values[row_index];
349
2/2
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 4 times.
13 for (std::size_t column_index = 0; column_index < row.size(); ++column_index) {
350
3/3
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 4 times.
✓ Branch 3 taken 5 times.
9 if (column_index != 0) sql += ", ";
351
2/2
✓ Branch 2 taken 9 times.
✓ Branch 5 taken 9 times.
9 sql += render_value_expression(row[column_index]);
352
5/5
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 2 times.
✓ Branch 4 taken 7 times.
✓ Branch 7 taken 7 times.
✓ Branch 10 taken 7 times.
9 if (row_index == 0) sql += " AS " + render_column_name(value.columns[column_index]);
353 }
354
2/2
✓ Branch 1 taken 4 times.
✓ Branch 4 taken 4 times.
4 sql += render_implicit_select_source();
355 }
356
3/3
✓ Branch 1 taken 3 times.
✓ Branch 4 taken 3 times.
✓ Branch 7 taken 3 times.
3 return sql + ")" + render_relation_alias(source_alias);
357 3 }
358 }
359 ✗ throw render_error{"unknown MERGE source style"};
360 }
361
362 44 static bool contains_column(const std::vector<detail::expression_ptr>& columns,
363 const detail::expression_ptr& expected)
364 {
365
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 44 times.
44 if (!expected) return false;
366
2/2
✓ Branch 5 taken 44 times.
✓ Branch 6 taken 19 times.
63 for (const detail::expression_ptr& column : columns) {
367
6/8
✓ Branch 1 taken 44 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 44 times.
✗ Branch 5 not taken.
✓ Branch 9 taken 25 times.
✓ Branch 10 taken 19 times.
✓ Branch 11 taken 25 times.
✓ Branch 12 taken 19 times.
69 if (column && column->kind == detail::expression_kind::column && column->text == expected->text &&
368
2/3
✓ Branch 3 taken 25 times.
✓ Branch 5 taken 25 times.
✗ Branch 6 not taken.
25 column->qualifier == expected->qualifier) {
369 25 return true;
370 }
371 }
372 19 return false;
373 }
374
375 12 static std::vector<detail::assignment_node> inserted_value_assignments(const detail::query_node& value)
376 {
377 12 std::vector<detail::assignment_node> assignments;
378
2/2
✓ Branch 5 taken 30 times.
✓ Branch 6 taken 12 times.
42 for (const detail::expression_ptr& column : value.columns) {
379
3/3
✓ Branch 1 taken 30 times.
✓ Branch 3 taken 12 times.
✓ Branch 4 taken 18 times.
30 if (contains_column(value.conflict.target, column)) continue;
380
3/6
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 18 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 18 times.
18 if (!column || column->kind != detail::expression_kind::column)
381 ✗ throw render_error{"proposed-value updates require INSERT column expressions"};
382
383
1/1
✓ Branch 1 taken 18 times.
18 std::shared_ptr<detail::expression_node> inserted = std::make_shared<detail::expression_node>();
384 18 inserted->kind = detail::expression_kind::inserted_column;
385
1/1
✓ Branch 3 taken 18 times.
18 inserted->qualifier = column->qualifier;
386
1/1
✓ Branch 3 taken 18 times.
18 inserted->text = column->text;
387 18 assignments.push_back({column, std::move(inserted)});
388 18 }
389 12 return assignments;
390 18 }
391
392 14 static std::vector<detail::assignment_node> conflict_assignments(const detail::query_node& value)
393 {
394
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 8 times.
14 if (value.conflict.action == detail::conflict_clause_node::kind::update_inserted_values)
395 6 return inserted_value_assignments(value);
396 8 return value.conflict.assignments;
397 }
398
399 3 static bool equivalent_inserted_value_assignments(const std::vector<detail::assignment_node>& actual,
400 const std::vector<detail::assignment_node>& expected)
401 {
402
2/2
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 2 times.
3 if (actual.size() != expected.size()) return false;
403
2/2
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 1 time.
3 for (const detail::assignment_node& expected_assignment : expected) {
404 2 bool found = false;
405
1/2
✓ Branch 5 taken 2 times.
✗ Branch 6 not taken.
2 for (const detail::assignment_node& actual_assignment : actual) {
406
1/2
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
4 if (!actual_assignment.column || !expected_assignment.column ||
407
5/9
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 5 taken 2 times.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✓ Branch 10 taken 2 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 2 times.
6 actual_assignment.column->qualifier != expected_assignment.column->qualifier ||
408 2 actual_assignment.column->text != expected_assignment.column->text) {
409 ✗ continue;
410 }
411
1/2
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
4 if (!actual_assignment.value || !expected_assignment.value ||
412
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
2 actual_assignment.value->kind != detail::expression_kind::inserted_column ||
413
6/9
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 5 taken 1 time.
✓ Branch 7 taken 1 time.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✓ Branch 10 taken 1 time.
✓ Branch 11 taken 1 time.
✓ Branch 12 taken 1 time.
5 actual_assignment.value->qualifier != expected_assignment.value->qualifier ||
414 1 actual_assignment.value->text != expected_assignment.value->text) {
415 1 return false;
416 }
417 1 found = true;
418 1 break;
419 }
420
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 if (!found) return false;
421 }
422 1 return true;
423 }
424
425 18 static bool is_default_value(const detail::expression_ptr& value)
426 {
427
2/6
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 18 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
18 return value && value->kind == detail::expression_kind::keyword && value->text == "default";
428 }
429
430 12 std::string render_update(const detail::query_node& value)
431 {
432
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 5 not taken.
12 if (value.assignments.empty()) throw render_error{"UPDATE requires at least one assignment"};
433
1/1
✓ Branch 1 taken 12 times.
12 std::string sql{"UPDATE "};
434
2/2
✓ Branch 1 taken 12 times.
✓ Branch 4 taken 12 times.
12 sql += render_mutation_target(value.target);
435
3/3
✓ Branch 4 taken 11 times.
✓ Branch 7 taken 11 times.
✓ Branch 10 taken 11 times.
15 sql += " SET " + render_assignments(value.assignments);
436
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 9 times.
11 if (value.from) {
437
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
2 if (!has_capability(m_options.capabilities, sql_capability::update_from))
438
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"UPDATE FROM is not supported by the selected preset"};
439
3/3
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
✓ Branch 7 taken 1 time.
1 sql += " FROM " + render_relation(value.from);
440 }
441
5/5
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 5 times.
✓ Branch 4 taken 5 times.
✓ Branch 7 taken 5 times.
✓ Branch 10 taken 5 times.
10 if (value.where) sql += " WHERE " + render_expression(value.where);
442
2/2
✓ Branch 1 taken 10 times.
✓ Branch 4 taken 10 times.
10 sql += render_returning(value.returning);
443 10 return sql;
444 2 }
445
446 8 std::string render_update_or_insert(const detail::query_node& value)
447 {
448 using conflict_kind = detail::conflict_clause_node::kind;
449
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 3 times.
8 if (value.conflict.action != conflict_kind::update &&
450
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 4 times.
5 value.conflict.action != conflict_kind::update_inserted_values) {
451
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"UPDATE OR INSERT requires an update conflict action"};
452 }
453
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✗ Branch 5 not taken.
7 if (value.conflict.target.empty()) throw render_error{"UPDATE OR INSERT requires an explicit conflict target"};
454
3/6
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 7 times.
7 if (value.default_values || value.insert_source)
455 ✗ throw render_error{"UPDATE OR INSERT requires an INSERT VALUES source"};
456
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✗ Branch 5 not taken.
7 if (value.columns.empty()) throw render_error{"UPDATE OR INSERT requires INSERT columns"};
457
3/3
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 6 times.
✓ Branch 5 taken 1 time.
7 if (value.values.size() != 1) throw render_error{"UPDATE OR INSERT requires exactly one VALUES row"};
458
1/2
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
6 if (value.values.front().size() != value.columns.size())
459 ✗ throw render_error{"INSERT row width does not match its column list"};
460
2/2
✓ Branch 5 taken 6 times.
✓ Branch 6 taken 6 times.
12 for (const detail::expression_ptr& target : value.conflict.target) {
461
2/3
✓ Branch 1 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
6 if (!contains_column(value.columns, target))
462 ✗ throw render_error{"UPDATE OR INSERT targets must be present in the INSERT column list"};
463 }
464
465
1/1
✓ Branch 1 taken 6 times.
6 const std::vector<detail::assignment_node> expected = inserted_value_assignments(value);
466
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 5 not taken.
6 if (expected.empty()) throw render_error{"UPDATE OR INSERT requires at least one non-target column"};
467
4/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 4 times.
9 if (value.conflict.action == conflict_kind::update &&
468
3/3
✓ Branch 1 taken 3 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 1 time.
3 !equivalent_inserted_value_assignments(value.conflict.assignments, expected)) {
469
1/1
✓ Branch 2 taken 2 times.
2 throw render_error{"UPDATE OR INSERT can only update every proposed non-target column with its inserted value"};
470 }
471
472
1/1
✓ Branch 1 taken 4 times.
4 std::string sql{"UPDATE OR INSERT INTO "};
473
2/2
✓ Branch 1 taken 4 times.
✓ Branch 4 taken 4 times.
4 sql += render_mutation_target(value.target);
474
4/4
✓ Branch 1 taken 4 times.
✓ Branch 4 taken 4 times.
✓ Branch 7 taken 4 times.
✓ Branch 10 taken 4 times.
4 sql += " (" + render_column_name_list(value.columns) + ") VALUES (";
475
3/3
✓ Branch 2 taken 4 times.
✓ Branch 5 taken 4 times.
✓ Branch 8 taken 4 times.
4 sql += render_value_expression_list(value.values.front()) + ")";
476
4/4
✓ Branch 1 taken 4 times.
✓ Branch 4 taken 4 times.
✓ Branch 7 taken 4 times.
✓ Branch 10 taken 4 times.
4 sql += " MATCHING (" + render_column_name_list(value.conflict.target) + ")";
477
2/2
✓ Branch 1 taken 4 times.
✓ Branch 4 taken 4 times.
4 sql += render_returning(value.returning);
478 8 return sql;
479 6 }
480
481 6 std::string render_delete(const detail::query_node& value)
482 {
483
1/1
✓ Branch 1 taken 6 times.
6 std::string sql{"DELETE FROM "};
484
2/2
✓ Branch 1 taken 6 times.
✓ Branch 4 taken 6 times.
6 sql += render_mutation_target(value.target);
485
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 4 times.
6 if (value.from) {
486
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
2 if (!has_capability(m_options.capabilities, sql_capability::delete_using))
487
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"DELETE USING is not supported by the selected preset"};
488
3/3
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
✓ Branch 7 taken 1 time.
1 sql += " USING " + render_relation(value.from);
489 }
490
4/5
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 5 times.
✓ Branch 7 taken 5 times.
✓ Branch 10 taken 5 times.
5 if (value.where) sql += " WHERE " + render_expression(value.where);
491
2/2
✓ Branch 1 taken 5 times.
✓ Branch 4 taken 5 times.
5 sql += render_returning(value.returning);
492 5 return sql;
493 1 }
494
495 77 std::string render_mutation_target(const detail::relation_ptr& value) const
496 {
497
4/8
✓ Branch 1 taken 77 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 77 times.
✗ Branch 5 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 77 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 77 times.
77 if (!value || value->kind != detail::relation_kind::table || value->name.empty())
498 ✗ throw render_error{"a mutation statement requires a table target"};
499
500
1/3
✗ Branch 2 not taken.
✓ Branch 3 taken 77 times.
✗ Branch 6 not taken.
77 if (!value->alias.empty()) throw render_error{"aliased mutation targets are not supported yet"};
501 77 return render_qualified_identifier(value->qualifiers, value->name);
502 }
503
504 69 std::string render_column_name_list(const std::vector<detail::expression_ptr>& values) const
505 {
506 69 std::string sql;
507
2/2
✓ Branch 1 taken 118 times.
✓ Branch 2 taken 69 times.
187 for (std::size_t index = 0; index < values.size(); ++index) {
508
3/3
✓ Branch 0 taken 49 times.
✓ Branch 1 taken 69 times.
✓ Branch 3 taken 49 times.
118 if (index != 0) sql += ", ";
509
2/2
✓ Branch 2 taken 118 times.
✓ Branch 5 taken 118 times.
118 sql += render_column_name(values[index]);
510 }
511 69 return sql;
512 ✗ }
513
514 181 std::string render_column_name(const detail::expression_ptr& value) const
515 {
516
4/8
✓ Branch 1 taken 181 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 181 times.
✗ Branch 5 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 181 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 181 times.
181 if (!value || value->kind != detail::expression_kind::column || value->text.empty())
517 ✗ throw render_error{"a mutation column list requires column expressions"};
518
519 181 return quote_identifier(value->text);
520 }
521
522 31 std::string render_assignments(const std::vector<detail::assignment_node>& values, bool conflict = false,
523 std::string inserted_row_alias = {},
524 std::vector<std::string> inserted_target_qualifier = {},
525 std::string merge_target_alias = {})
526 {
527
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 31 times.
✗ Branch 5 not taken.
31 if (values.empty()) throw render_error{"an assignment list cannot be empty"};
528 31 const bool previous_conflict_state = m_rendering_conflict_assignments;
529 31 std::string previous_inserted_row_alias = std::move(m_inserted_row_alias);
530 31 std::vector<std::string> previous_inserted_target_qualifier = std::move(m_inserted_target_qualifier);
531 31 std::string previous_merge_target_alias = std::move(m_merge_target_alias);
532 31 std::vector<std::string> previous_merge_target_qualifier = std::move(m_merge_target_qualifier);
533 31 m_rendering_conflict_assignments = conflict;
534 31 m_inserted_row_alias = std::move(inserted_row_alias);
535 31 m_inserted_target_qualifier = std::move(inserted_target_qualifier);
536 31 m_merge_target_alias = std::move(merge_target_alias);
537
1/1
✓ Branch 1 taken 31 times.
31 m_merge_target_qualifier = m_inserted_target_qualifier;
538 31 std::string sql;
539
2/2
✓ Branch 1 taken 36 times.
✓ Branch 2 taken 28 times.
64 for (std::size_t index = 0; index < values.size(); ++index) {
540
1/3
✗ Branch 2 not taken.
✓ Branch 3 taken 36 times.
✗ Branch 6 not taken.
36 if (!values[index].value) throw render_error{"an assignment requires a value"};
541
3/3
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 31 times.
✓ Branch 3 taken 5 times.
36 if (index != 0) sql += ", ";
542
2/2
✓ Branch 2 taken 36 times.
✓ Branch 5 taken 36 times.
36 sql += render_column_name(values[index].column);
543
1/1
✓ Branch 1 taken 36 times.
36 sql += " = ";
544
2/2
✓ Branch 2 taken 33 times.
✓ Branch 5 taken 33 times.
36 sql += render_value_expression(values[index].value);
545 }
546 28 m_rendering_conflict_assignments = previous_conflict_state;
547 28 m_inserted_row_alias = std::move(previous_inserted_row_alias);
548 28 m_inserted_target_qualifier = std::move(previous_inserted_target_qualifier);
549 28 m_merge_target_alias = std::move(previous_merge_target_alias);
550 28 m_merge_target_qualifier = std::move(previous_merge_target_qualifier);
551 56 return sql;
552 43 }
553
554 40 std::string render_conflict_clause(const detail::query_node& query, const std::string& inserted_row_alias,
555 const std::vector<std::string>& inserted_target_qualifier)
556 {
557 using conflict_kind = detail::conflict_clause_node::kind;
558 40 const detail::conflict_clause_node& value = query.conflict;
559
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 24 times.
40 if (value.action == conflict_kind::none) return {};
560
561
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 18 times.
24 if (value.action == conflict_kind::duplicate_key_update) {
562
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 5 times.
6 if (!has_capability(m_options.capabilities, sql_capability::on_duplicate_key_update))
563
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"ON DUPLICATE KEY UPDATE is not supported by the selected preset"};
564
565 return " ON DUPLICATE KEY UPDATE " +
566
4/4
✓ Branch 2 taken 5 times.
✓ Branch 5 taken 5 times.
✓ Branch 8 taken 5 times.
✓ Branch 11 taken 5 times.
5 render_assignments(value.assignments, true, inserted_row_alias, inserted_target_qualifier);
567 }
568
569
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 15 times.
18 if (!has_capability(m_options.capabilities, sql_capability::on_conflict))
570
1/1
✓ Branch 2 taken 3 times.
3 throw render_error{"ON CONFLICT is not supported by the selected preset"};
571
572
1/1
✓ Branch 1 taken 15 times.
15 std::string sql{" ON CONFLICT"};
573
6/6
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 3 times.
✓ Branch 4 taken 12 times.
✓ Branch 7 taken 12 times.
✓ Branch 10 taken 12 times.
✓ Branch 13 taken 12 times.
15 if (!value.target.empty()) sql += " (" + render_column_name_list(value.target) + ")";
574
3/3
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 10 times.
✓ Branch 3 taken 5 times.
15 if (value.action == conflict_kind::do_nothing) return sql + " DO NOTHING";
575
576
3/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
10 if (value.action == conflict_kind::update || value.action == conflict_kind::update_inserted_values) {
577
6/6
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 1 time.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 9 times.
12 if (value.target.empty() &&
578 2 has_capability(m_options.capabilities, sql_capability::conflict_update_requires_target)) {
579
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"ON CONFLICT DO UPDATE requires a conflict target for the selected preset"};
580 }
581
582
1/1
✓ Branch 1 taken 7 times.
14 return sql + " DO UPDATE SET " +
583
4/4
✓ Branch 2 taken 9 times.
✓ Branch 6 taken 9 times.
✓ Branch 9 taken 7 times.
✓ Branch 12 taken 7 times.
31 render_assignments(conflict_assignments(query), true, {}, inserted_target_qualifier);
584 }
585
586 ✗ throw render_error{"unknown conflict action"};
587 15 }
588
589 52 std::string render_returning(const std::vector<detail::expression_ptr>& values)
590 {
591
2/2
✓ Branch 1 taken 41 times.
✓ Branch 2 taken 11 times.
52 if (values.empty()) return {};
592
593
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 10 times.
11 if (!has_capability(m_options.capabilities, sql_capability::returning))
594
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"RETURNING is not supported by the selected preset"};
595
596
2/2
✓ Branch 1 taken 10 times.
✓ Branch 4 taken 10 times.
10 return " RETURNING " + render_result_expression_list(values);
597 }
598
599 285 std::string render_ctes(const detail::query_node& value)
600 {
601
2/2
✓ Branch 1 taken 273 times.
✓ Branch 2 taken 12 times.
285 if (value.ctes.empty()) {
602
1/3
✗ Branch 0 not taken.
✓ Branch 1 taken 273 times.
✗ Branch 4 not taken.
273 if (value.recursive) throw render_error{"a recursive query requires at least one CTE"};
603 273 return {};
604 }
605
606
6/6
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 6 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 11 times.
12 if (value.recursive && !has_capability(m_options.capabilities, sql_capability::recursive_cte))
607
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"recursive CTEs are not supported by the selected SQL options"};
608
609
1/1
✓ Branch 1 taken 11 times.
11 std::string sql{"WITH "};
610
5/5
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 2 times.
✓ Branch 5 taken 3 times.
11 if (value.recursive && m_options.recursive_ctes == recursive_cte_style::recursive_keyword) sql = "WITH RECURSIVE ";
611 11 std::unordered_set<std::string> cte_names;
612
2/2
✓ Branch 1 taken 14 times.
✓ Branch 2 taken 8 times.
22 for (std::size_t index = 0; index < value.ctes.size(); ++index) {
613 14 const detail::common_table_expression_node& cte = value.ctes[index];
614
3/7
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 14 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 14 times.
✗ Branch 10 not taken.
14 if (cte.name.empty() || !cte.query) throw render_error{"a CTE requires a name and query"};
615
4/4
✓ Branch 1 taken 14 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 13 times.
✓ Branch 7 taken 1 time.
14 if (!cte_names.emplace(cte.name).second) throw render_error{"CTE names must be unique within a query"};
616
4/4
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 10 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 11 times.
16 if (cte.query->kind != detail::statement_kind::select &&
617
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 1 time.
3 !has_capability(m_options.capabilities, sql_capability::data_modifying_cte)) {
618
1/1
✓ Branch 2 taken 2 times.
2 throw render_error{"data-modifying CTEs are not supported by the selected preset"};
619 }
620
3/3
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 9 times.
✓ Branch 3 taken 2 times.
11 if (index != 0) sql += ", ";
621
2/2
✓ Branch 1 taken 11 times.
✓ Branch 4 taken 11 times.
11 sql += quote_identifier(cte.name);
622
2/2
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 4 times.
11 if (!cte.columns.empty()) {
623
1/1
✓ Branch 1 taken 7 times.
7 sql += " (";
624
2/2
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 7 times.
14 for (std::size_t column_index = 0; column_index < cte.columns.size(); ++column_index) {
625
1/3
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
✗ Branch 3 not taken.
7 if (column_index != 0) sql += ", ";
626
2/2
✓ Branch 2 taken 7 times.
✓ Branch 5 taken 7 times.
7 sql += quote_identifier(cte.columns[column_index]);
627 }
628
1/1
✓ Branch 1 taken 7 times.
7 sql += ")";
629 }
630
631
1/1
✓ Branch 1 taken 11 times.
11 sql += " AS (";
632
2/2
✓ Branch 2 taken 11 times.
✓ Branch 5 taken 11 times.
11 sql += render_query_node(*cte.query);
633
1/1
✓ Branch 1 taken 11 times.
11 sql += ")";
634 }
635
1/1
✓ Branch 1 taken 8 times.
8 return sql + " ";
636 14 }
637
638 18 std::string set_operation_keyword(detail::set_operation_kind kind) const
639 {
640
4/5
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
18 switch (kind) {
641 5 case detail::set_operation_kind::union_:
642
1/1
✓ Branch 1 taken 5 times.
10 return "UNION";
643 6 case detail::set_operation_kind::union_all:
644
1/1
✓ Branch 1 taken 6 times.
12 return "UNION ALL";
645 4 case detail::set_operation_kind::intersect:
646
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 3 times.
4 if (!has_capability(m_options.capabilities, sql_capability::intersect))
647
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"INTERSECT is not supported by the selected SQL options"};
648
1/1
✓ Branch 1 taken 3 times.
6 return "INTERSECT";
649 3 case detail::set_operation_kind::except:
650
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 2 times.
3 if (!has_capability(m_options.capabilities, sql_capability::except))
651
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"EXCEPT is not supported by the selected SQL options"};
652
3/3
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
✓ Branch 3 taken 2 times.
4 return m_options.except_operator == except_style::minus_keyword ? "MINUS" : "EXCEPT";
653 }
654 ✗ throw render_error{"unknown set operation kind"};
655 }
656
657 18 static int set_operation_precedence(detail::set_operation_kind kind)
658 {
659
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 14 times.
18 return kind == detail::set_operation_kind::intersect ? 20 : 10;
660 }
661
662 16 std::string render_set_operand(const detail::query_node& value)
663 {
664 16 std::string sql = render_query_node(value);
665
9/12
✓ Branch 1 taken 14 times.
✓ Branch 2 taken 2 times.
✓ Branch 4 taken 13 times.
✓ Branch 5 taken 1 time.
✓ Branch 7 taken 13 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 13 times.
✗ Branch 11 not taken.
✗ Branch 13 not taken.
✓ Branch 14 taken 13 times.
✓ Branch 15 taken 3 times.
✓ Branch 16 taken 13 times.
16 if (!value.set_operations.empty() || !value.order_by.empty() || value.limit || value.offset || !value.ctes.empty())
666
1/1
✓ Branch 3 taken 3 times.
3 sql = group_set_sql(std::move(sql));
667 16 return sql;
668 ✗ }
669
670 5 std::string group_set_sql(std::string sql) const
671 {
672
4/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
✓ Branch 3 taken 3 times.
✓ Branch 6 taken 3 times.
5 if (m_options.set_operands == set_operand_style::parenthesized) return "(" + sql + ")";
673
5/5
✓ Branch 1 taken 2 times.
✓ Branch 4 taken 2 times.
✓ Branch 7 taken 2 times.
✓ Branch 10 taken 2 times.
✓ Branch 13 taken 2 times.
4 return "SELECT * FROM (" + sql + ")" + render_relation_alias("_sqlon_set_operand");
674 }
675
676 23 std::string render_expression_list(const std::vector<detail::expression_ptr>& values)
677 {
678 23 std::string sql;
679
2/2
✓ Branch 1 taken 23 times.
✓ Branch 2 taken 23 times.
46 for (std::size_t index = 0; index < values.size(); ++index) {
680
3/3
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 14 times.
✓ Branch 3 taken 9 times.
23 if (index != 0) sql += ", ";
681
2/2
✓ Branch 2 taken 23 times.
✓ Branch 5 taken 23 times.
23 sql += render_expression(values[index]);
682 }
683 23 return sql;
684 ✗ }
685
686 10 std::string render_result_expression_list(const std::vector<detail::expression_ptr>& values)
687 {
688 10 std::string sql;
689
2/2
✓ Branch 1 taken 10 times.
✓ Branch 2 taken 10 times.
20 for (std::size_t index = 0; index < values.size(); ++index) {
690
1/3
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
✗ Branch 3 not taken.
10 if (index != 0) sql += ", ";
691
2/2
✓ Branch 2 taken 10 times.
✓ Branch 5 taken 10 times.
10 sql += render_expression(values[index], 0, expression_context::result);
692 }
693 10 return sql;
694 ✗ }
695
696 47 std::string render_value_expression_list(const std::vector<detail::expression_ptr>& values)
697 {
698 47 std::string sql;
699
2/2
✓ Branch 1 taken 91 times.
✓ Branch 2 taken 46 times.
137 for (std::size_t index = 0; index < values.size(); ++index) {
700
3/3
✓ Branch 0 taken 44 times.
✓ Branch 1 taken 47 times.
✓ Branch 3 taken 44 times.
91 if (index != 0) sql += ", ";
701
2/2
✓ Branch 2 taken 90 times.
✓ Branch 5 taken 90 times.
91 sql += render_value_expression(values[index]);
702 }
703 46 return sql;
704 1 }
705
706 136 std::string render_value_expression(const detail::expression_ptr& value)
707 {
708
7/8
✓ Branch 1 taken 136 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✓ Branch 5 taken 130 times.
✓ Branch 8 taken 5 times.
✓ Branch 9 taken 1 time.
✓ Branch 10 taken 5 times.
✓ Branch 11 taken 131 times.
136 if (value && value->kind == detail::expression_kind::keyword && value->text == "default") {
709
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 3 times.
5 if (!has_capability(m_options.capabilities, sql_capability::default_value))
710
1/1
✓ Branch 2 taken 2 times.
2 throw render_error{"DEFAULT values are not supported by the selected preset"};
711
1/1
✓ Branch 1 taken 3 times.
6 return "DEFAULT";
712 }
713 131 return render_expression(value);
714 }
715
716 19 std::string render_order_by(const std::vector<detail::order_by_node>& values)
717 {
718 19 std::string sql;
719
2/2
✓ Branch 1 taken 21 times.
✓ Branch 2 taken 18 times.
39 for (std::size_t index = 0; index < values.size(); ++index) {
720
1/3
✗ Branch 2 not taken.
✓ Branch 3 taken 21 times.
✗ Branch 6 not taken.
21 if (!values[index].expression) throw render_error{"ORDER BY requires an expression"};
721
4/4
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 15 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 20 times.
27 if (values[index].nulls != detail::nulls_order::unspecified &&
722
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 5 times.
6 !has_capability(m_options.capabilities, sql_capability::nulls_ordering)) {
723
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"NULLS FIRST/LAST is not supported by the selected SQL options"};
724 }
725
3/3
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 18 times.
✓ Branch 3 taken 2 times.
20 if (index != 0) sql += ", ";
726
2/2
✓ Branch 2 taken 20 times.
✓ Branch 5 taken 20 times.
20 sql += render_expression(values[index].expression);
727
3/3
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 11 times.
✓ Branch 4 taken 20 times.
20 sql += values[index].direction == detail::order_direction::descending ? " DESC" : " ASC";
728
3/3
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 19 times.
✓ Branch 4 taken 1 time.
20 if (values[index].nulls == detail::nulls_order::first) sql += " NULLS FIRST";
729
3/3
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 16 times.
✓ Branch 4 taken 4 times.
20 if (values[index].nulls == detail::nulls_order::last) sql += " NULLS LAST";
730 }
731 18 return sql;
732 1 }
733
734 892 std::string render_expression(const detail::expression_ptr& value, int parent_precedence = 0,
735 expression_context context = expression_context::value)
736 {
737
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 892 times.
✗ Branch 5 not taken.
892 if (!value) throw render_error{"cannot render an empty expression node"};
738
739
19/19
✓ Branch 1 taken 278 times.
✓ Branch 2 taken 270 times.
✓ Branch 3 taken 34 times.
✓ Branch 4 taken 41 times.
✓ Branch 5 taken 20 times.
✓ Branch 6 taken 6 times.
✓ Branch 7 taken 16 times.
✓ Branch 8 taken 118 times.
✓ Branch 9 taken 9 times.
✓ Branch 10 taken 27 times.
✓ Branch 11 taken 15 times.
✓ Branch 12 taken 2 times.
✓ Branch 13 taken 17 times.
✓ Branch 14 taken 2 times.
✓ Branch 15 taken 27 times.
✓ Branch 16 taken 1 time.
✓ Branch 17 taken 6 times.
✓ Branch 18 taken 2 times.
✓ Branch 19 taken 1 time.
892 switch (value->kind) {
740 278 case detail::expression_kind::column:
741 278 return render_column(*value);
742 270 case detail::expression_kind::parameter:
743 270 return render_parameter(*value);
744 34 case detail::expression_kind::parameter_slot:
745 34 return render_parameter_slot(*value);
746 41 case detail::expression_kind::literal:
747 41 return render_literal(*value);
748 20 case detail::expression_kind::inserted_column:
749 20 return render_inserted_column(*value);
750 6 case detail::expression_kind::keyword:
751 6 return render_keyword(*value);
752 16 case detail::expression_kind::alias:
753
1/3
✗ Branch 2 not taken.
✓ Branch 3 taken 16 times.
✗ Branch 6 not taken.
16 if (value->operands.size() != 1) throw render_error{"an aliased expression requires one operand"};
754
1/3
✗ Branch 2 not taken.
✓ Branch 3 taken 16 times.
✗ Branch 6 not taken.
16 if (value->text.empty()) throw render_error{"an aliased expression requires a name"};
755
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 2 times.
16 if (context == expression_context::result)
756
4/4
✓ Branch 2 taken 14 times.
✓ Branch 7 taken 14 times.
✓ Branch 10 taken 14 times.
✓ Branch 13 taken 14 times.
14 return render_expression(value->operands.front()) + " AS " + quote_identifier(value->text);
757 2 return render_expression(value->operands.front(), parent_precedence);
758 118 case detail::expression_kind::binary:
759 118 return render_binary(*value, parent_precedence);
760 9 case detail::expression_kind::unary:
761 9 return render_unary(*value, parent_precedence);
762 27 case detail::expression_kind::function:
763 27 return render_function(*value);
764 15 case detail::expression_kind::function_call:
765 15 return render_function_call(*value);
766 2 case detail::expression_kind::distinct:
767
1/3
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 6 not taken.
2 if (value->operands.size() != 1) throw render_error{"DISTINCT requires one expression"};
768
2/2
✓ Branch 3 taken 2 times.
✓ Branch 6 taken 2 times.
2 return "DISTINCT " + render_expression(value->operands.front());
769 17 case detail::expression_kind::window:
770 17 return render_window(*value);
771 2 case detail::expression_kind::case_:
772 2 return render_case(*value);
773 27 case detail::expression_kind::raw_expression:
774 27 return render_raw_expression(*value);
775 1 case detail::expression_kind::raw:
776
1/3
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
✗ Branch 6 not taken.
1 if (value->text.empty()) throw render_error{"a raw SQL expression cannot be empty"};
777 1 return value->text;
778 6 case detail::expression_kind::subquery:
779
1/3
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
✗ Branch 6 not taken.
6 if (!value->subquery) throw render_error{"a subquery expression requires a query"};
780
3/3
✓ Branch 3 taken 5 times.
✓ Branch 6 taken 5 times.
✓ Branch 9 taken 5 times.
6 return "(" + render_query_node(*value->subquery) + ")";
781 2 case detail::expression_kind::exists:
782
1/3
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 6 not taken.
2 if (!value->subquery) throw render_error{"EXISTS requires a subquery"};
783
3/3
✓ Branch 3 taken 2 times.
✓ Branch 6 taken 2 times.
✓ Branch 9 taken 2 times.
2 return "EXISTS (" + render_query_node(*value->subquery) + ")";
784 1 default:
785
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"this expression kind is not implemented yet"};
786 }
787 }
788
789 27 std::string render_raw_expression(const detail::expression_node& value)
790 {
791
3/3
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 26 times.
✓ Branch 5 taken 1 time.
27 if (value.text.empty()) throw render_error{"a raw expression template cannot be empty"};
792
3/3
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 25 times.
✓ Branch 5 taken 1 time.
26 if (value.operands.empty()) throw render_error{"a raw expression requires at least one operand"};
793
794
1/1
✓ Branch 1 taken 25 times.
25 std::string sql{"("};
795 25 std::size_t operand_index = 0;
796
2/2
✓ Branch 1 taken 218 times.
✓ Branch 2 taken 22 times.
240 for (std::size_t index = 0; index < value.text.size(); ++index) {
797 218 const char character = value.text[index];
798
2/2
✓ Branch 0 taken 47 times.
✓ Branch 1 taken 171 times.
218 if (character == '{') {
799
5/6
✓ Branch 1 taken 46 times.
✓ Branch 2 taken 1 time.
✗ Branch 4 not taken.
✓ Branch 5 taken 46 times.
✓ Branch 6 taken 1 time.
✓ Branch 7 taken 46 times.
47 if (index + 1 >= value.text.size() || value.text[index + 1] != '}')
800
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"a raw expression template contains malformed syntax"};
801
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 45 times.
46 if (operand_index >= value.operands.size())
802
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"a raw expression has fewer operands than {} positions"};
803
2/2
✓ Branch 2 taken 45 times.
✓ Branch 5 taken 45 times.
45 sql += render_expression(value.operands[operand_index++]);
804 45 ++index;
805
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 170 times.
171 } else if (character == '}') {
806
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"a raw expression template contains malformed syntax"};
807 } else {
808
1/1
✓ Branch 1 taken 170 times.
170 sql += character;
809 }
810 }
811
812
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 21 times.
22 if (operand_index != value.operands.size())
813
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"a raw expression has more operands than {} positions"};
814
1/1
✓ Branch 1 taken 21 times.
42 return sql + ")";
815 25 }
816
817 118 std::string render_binary(const detail::expression_node& value, int parent_precedence)
818 {
819
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 118 times.
✗ Branch 5 not taken.
118 if (value.operands.size() != 2) throw render_error{"a binary expression requires two operands"};
820 118 const int precedence = binary_precedence(value.text);
821 118 std::string sql = render_expression(value.operands[0], precedence);
822
4/4
✓ Branch 1 taken 118 times.
✓ Branch 4 taken 118 times.
✓ Branch 7 taken 118 times.
✓ Branch 10 taken 118 times.
118 sql += " " + binary_operator(value.text) + " ";
823
2/2
✓ Branch 2 taken 116 times.
✓ Branch 5 taken 116 times.
118 sql += render_expression(value.operands[1], precedence + 1);
824
4/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 113 times.
✓ Branch 3 taken 3 times.
✓ Branch 6 taken 3 times.
116 if (precedence < parent_precedence) sql = "(" + sql + ")";
825 116 return sql;
826 2 }
827
828 9 std::string render_unary(const detail::expression_node& value, int parent_precedence)
829 {
830
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
✗ Branch 5 not taken.
9 if (value.operands.size() != 1) throw render_error{"a unary expression requires one operand"};
831
5/5
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 6 times.
✓ Branch 5 taken 3 times.
✓ Branch 8 taken 3 times.
✓ Branch 11 taken 3 times.
9 if (value.text == "group") return "(" + render_expression(value.operands[0]) + ")";
832
4/4
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 5 times.
✓ Branch 5 taken 1 time.
✓ Branch 8 taken 1 time.
6 if (value.text == "is_null") return render_expression(value.operands[0], 30) + " IS NULL";
833
4/4
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 2 times.
✓ Branch 5 taken 3 times.
✓ Branch 8 taken 3 times.
5 if (value.text == "is_not_null") return render_expression(value.operands[0], 30) + " IS NOT NULL";
834
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 if (value.text == "not") {
835 2 constexpr int precedence = 25;
836
2/2
✓ Branch 2 taken 2 times.
✓ Branch 5 taken 2 times.
2 std::string sql = "NOT " + render_expression(value.operands[0], precedence);
837
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✗ Branch 3 not taken.
✗ Branch 6 not taken.
2 if (precedence < parent_precedence) sql = "(" + sql + ")";
838 2 return sql;
839 2 }
840
841 ✗ throw render_error{"unknown unary expression operation"};
842 }
843
844 27 std::string render_function(const detail::expression_node& value)
845 {
846
6/6
✓ Branch 1 taken 23 times.
✓ Branch 2 taken 4 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 21 times.
✓ Branch 6 taken 6 times.
✓ Branch 7 taken 21 times.
27 if (value.text == "in" || value.text == "not_in") {
847
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 5 not taken.
6 if (value.operands.size() < 2) throw render_error{"IN requires at least one value"};
848
5/6
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 3 times.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 3 times.
✓ Branch 8 taken 3 times.
6 if (value.operands.size() == 2 && value.operands[1]->kind == detail::expression_kind::subquery) {
849
1/1
✓ Branch 2 taken 3 times.
3 std::string sql = render_expression(value.operands.front(), 30);
850
4/4
✓ Branch 1 taken 3 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 2 times.
✓ Branch 6 taken 3 times.
3 sql += value.text == "not_in" ? " NOT IN " : " IN ";
851
2/2
✓ Branch 2 taken 3 times.
✓ Branch 5 taken 3 times.
3 sql += render_expression(value.operands[1]);
852 3 return sql;
853 3 }
854
855
1/1
✓ Branch 2 taken 3 times.
3 std::string sql = render_expression(value.operands.front(), 30);
856
4/4
✓ Branch 1 taken 3 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 2 times.
✓ Branch 6 taken 3 times.
3 sql += value.text == "not_in" ? " NOT IN (" : " IN (";
857
2/2
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 3 times.
12 for (std::size_t index = 1; index < value.operands.size(); ++index) {
858
3/3
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 3 times.
✓ Branch 3 taken 6 times.
9 if (index != 1) sql += ", ";
859
2/2
✓ Branch 2 taken 9 times.
✓ Branch 5 taken 9 times.
9 sql += render_expression(value.operands[index]);
860 }
861
1/1
✓ Branch 1 taken 3 times.
3 return sql + ")";
862 3 }
863
864
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 19 times.
21 if (value.text == "between") {
865
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 5 not taken.
2 if (value.operands.size() != 3) throw render_error{"BETWEEN requires a lower and upper bound"};
866
1/1
✓ Branch 2 taken 2 times.
2 std::string sql = render_expression(value.operands[0], 30);
867
1/1
✓ Branch 1 taken 2 times.
2 sql += " BETWEEN ";
868
2/2
✓ Branch 2 taken 2 times.
✓ Branch 5 taken 2 times.
2 sql += render_expression(value.operands[1]);
869
1/1
✓ Branch 1 taken 2 times.
2 sql += " AND ";
870
2/2
✓ Branch 2 taken 2 times.
✓ Branch 5 taken 2 times.
2 sql += render_expression(value.operands[2]);
871 2 return sql;
872 2 }
873
874
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 18 times.
19 if (value.text == "count_all") {
875
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 5 not taken.
1 if (!value.operands.empty()) throw render_error{"COUNT(*) does not accept operands"};
876
1/1
✓ Branch 1 taken 1 time.
2 return "COUNT(*)";
877 }
878
879
9/10
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 6 times.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 8 times.
✓ Branch 7 taken 3 times.
✓ Branch 8 taken 1 time.
✓ Branch 10 taken 1 time.
✓ Branch 11 taken 2 times.
✓ Branch 12 taken 18 times.
✗ Branch 13 not taken.
19 if (value.text == "count" || value.text == "sum" || value.text == "min" || value.text == "max" ||
880
1/2
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
1 value.text == "avg") {
881
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✗ Branch 5 not taken.
18 if (value.operands.size() != 1) throw render_error{"an aggregate function requires one operand"};
882
1/1
✓ Branch 1 taken 18 times.
18 std::string name = value.text;
883
2/2
✓ Branch 4 taken 66 times.
✓ Branch 5 taken 18 times.
84 for (char& character : name)
884 66 character = static_cast<char>(std::toupper(static_cast<unsigned char>(character)));
885
4/4
✓ Branch 2 taken 18 times.
✓ Branch 5 taken 18 times.
✓ Branch 8 taken 18 times.
✓ Branch 11 taken 18 times.
18 return name + "(" + render_expression(value.operands.front()) + ")";
886 18 }
887
888 ✗ throw render_error{"unknown SQL function expression"};
889 }
890
891 15 std::string render_function_call(const detail::expression_node& value)
892 {
893
1/3
✗ Branch 2 not taken.
✓ Branch 3 taken 15 times.
✗ Branch 6 not taken.
15 if (!detail::valid_function_name(value.text)) throw render_error{"a SQL function has an invalid name"};
894
1/1
✓ Branch 1 taken 15 times.
15 std::string name = value.text;
895
2/2
✓ Branch 4 taken 90 times.
✓ Branch 5 taken 15 times.
105 for (char& character : name)
896 90 character = static_cast<char>(std::toupper(static_cast<unsigned char>(character)));
897
4/4
✓ Branch 1 taken 15 times.
✓ Branch 4 taken 15 times.
✓ Branch 7 taken 15 times.
✓ Branch 10 taken 15 times.
30 return name + "(" + render_expression_list(value.operands) + ")";
898 15 }
899
900 17 std::string render_window(const detail::expression_node& value)
901 {
902
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 16 times.
17 if (!has_capability(m_options.capabilities, sql_capability::window_functions))
903
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"window functions are not supported by the selected preset"};
904
3/6
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 16 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 16 times.
16 if (value.operands.size() != 1 || !value.window)
905 ✗ throw render_error{"a window expression requires one expression and a window specification"};
906
5/5
✓ Branch 2 taken 16 times.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 12 times.
✓ Branch 6 taken 3 times.
✓ Branch 7 taken 13 times.
20 if (contains_distinct(value.operands.front()) &&
907
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 1 time.
4 !has_capability(m_options.capabilities, sql_capability::distinct_window_aggregates)) {
908
1/1
✓ Branch 2 taken 3 times.
3 throw render_error{"DISTINCT window aggregates are not supported by the selected preset"};
909 }
910
911 13 const detail::window_spec_node& window = *value.window;
912
2/2
✓ Branch 2 taken 13 times.
✓ Branch 5 taken 13 times.
13 std::string sql = render_expression(value.operands.front()) + " OVER (";
913 13 bool has_clause = false;
914
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 9 times.
13 if (!window.partition_by.empty()) {
915
3/3
✓ Branch 1 taken 4 times.
✓ Branch 4 taken 4 times.
✓ Branch 7 taken 4 times.
4 sql += "PARTITION BY " + render_expression_list(window.partition_by);
916 4 has_clause = true;
917 }
918
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 9 times.
13 if (!window.order_by.empty()) {
919
2/3
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 4 times.
4 if (has_clause) sql += " ";
920
3/3
✓ Branch 1 taken 4 times.
✓ Branch 4 taken 4 times.
✓ Branch 7 taken 4 times.
4 sql += "ORDER BY " + render_order_by(window.order_by);
921 4 has_clause = true;
922 }
923
2/2
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 6 times.
13 if (window.frame) {
924
3/3
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 6 times.
✓ Branch 3 taken 1 time.
7 if (has_clause) sql += " ";
925
2/2
✓ Branch 2 taken 6 times.
✓ Branch 5 taken 6 times.
7 sql += render_window_frame(*window.frame);
926 }
927
1/1
✓ Branch 1 taken 12 times.
24 return sql + ")";
928 13 }
929
930 7 std::string render_window_frame(const detail::window_frame_node& value)
931 {
932 7 std::string sql;
933
3/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
7 switch (value.mode) {
934 4 case detail::window_frame_mode::rows:
935
1/1
✓ Branch 1 taken 4 times.
4 sql = "ROWS ";
936 4 break;
937 1 case detail::window_frame_mode::range:
938
1/1
✓ Branch 1 taken 1 time.
1 sql = "RANGE ";
939 1 break;
940 2 case detail::window_frame_mode::groups:
941
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
2 if (!has_capability(m_options.capabilities, sql_capability::groups_window_frame))
942
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"GROUPS window frames are not supported by the selected preset"};
943
1/1
✓ Branch 1 taken 1 time.
1 sql = "GROUPS ";
944 1 break;
945 }
946
947
4/4
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 3 times.
✓ Branch 4 taken 3 times.
✓ Branch 7 taken 3 times.
6 if (!value.end) return sql + render_window_bound(value.start);
948
6/6
✓ Branch 2 taken 3 times.
✓ Branch 5 taken 3 times.
✓ Branch 8 taken 3 times.
✓ Branch 11 taken 3 times.
✓ Branch 14 taken 3 times.
✓ Branch 17 taken 3 times.
3 return sql + "BETWEEN " + render_window_bound(value.start) + " AND " + render_window_bound(*value.end);
949 7 }
950
951 9 static std::string render_window_bound(const detail::window_frame_bound_node& value)
952 {
953
5/6
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
9 switch (value.kind) {
954 2 case detail::window_frame_bound_kind::unbounded_preceding:
955
1/1
✓ Branch 1 taken 2 times.
4 return "UNBOUNDED PRECEDING";
956 2 case detail::window_frame_bound_kind::preceding:
957
2/2
✓ Branch 1 taken 2 times.
✓ Branch 4 taken 2 times.
2 return std::to_string(value.offset) + " PRECEDING";
958 3 case detail::window_frame_bound_kind::current_row:
959
1/1
✓ Branch 1 taken 3 times.
6 return "CURRENT ROW";
960 1 case detail::window_frame_bound_kind::following:
961
2/2
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
1 return std::to_string(value.offset) + " FOLLOWING";
962 1 case detail::window_frame_bound_kind::unbounded_following:
963
1/1
✓ Branch 1 taken 1 time.
2 return "UNBOUNDED FOLLOWING";
964 }
965 ✗ throw render_error{"unknown window frame bound"};
966 }
967
968 27 static bool contains_distinct(const detail::expression_ptr& value)
969 {
970
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 27 times.
27 if (!value) return false;
971
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 23 times.
27 if (value->kind == detail::expression_kind::distinct) return true;
972
2/2
✓ Branch 6 taken 11 times.
✓ Branch 7 taken 19 times.
30 for (const detail::expression_ptr& operand : value->operands)
973
3/3
✓ Branch 1 taken 11 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 7 times.
11 if (contains_distinct(operand)) return true;
974 19 return false;
975 }
976
977 2 std::string render_case(const detail::expression_node& value)
978 {
979
3/6
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 2 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 2 times.
2 if (value.operands.size() < 3 || value.operands.size() % 2 == 0)
980 ✗ throw render_error{"CASE requires one or more WHEN branches and an ELSE expression"};
981
982
1/1
✓ Branch 1 taken 2 times.
2 std::string sql{"CASE"};
983
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 2 times.
6 for (std::size_t index = 0; index + 1 < value.operands.size(); index += 2) {
984
1/1
✓ Branch 1 taken 4 times.
4 sql += " WHEN ";
985
2/2
✓ Branch 2 taken 4 times.
✓ Branch 5 taken 4 times.
4 sql += render_expression(value.operands[index]);
986
1/1
✓ Branch 1 taken 4 times.
4 sql += " THEN ";
987
2/2
✓ Branch 2 taken 4 times.
✓ Branch 5 taken 4 times.
4 sql += render_expression(value.operands[index + 1]);
988 }
989
4/4
✓ Branch 2 taken 2 times.
✓ Branch 5 taken 2 times.
✓ Branch 8 taken 2 times.
✓ Branch 11 taken 2 times.
2 sql += " ELSE " + render_expression(value.operands.back()) + " END";
990 2 return sql;
991 ✗ }
992
993 118 static int binary_precedence(const std::string& operation)
994 {
995
2/2
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 109 times.
118 if (operation == "or") return 10;
996
2/2
✓ Branch 1 taken 17 times.
✓ Branch 2 taken 92 times.
109 if (operation == "and") return 20;
997
8/8
✓ Branch 2 taken 32 times.
✓ Branch 3 taken 1 time.
✓ Branch 5 taken 30 times.
✓ Branch 6 taken 2 times.
✓ Branch 8 taken 29 times.
✓ Branch 9 taken 1 time.
✓ Branch 11 taken 14 times.
✓ Branch 12 taken 15 times.
125 if (operation == "=" || operation == "!=" || operation == "<" || operation == "<=" || operation == ">" ||
998
10/10
✓ Branch 0 taken 33 times.
✓ Branch 1 taken 59 times.
✓ Branch 3 taken 12 times.
✓ Branch 4 taken 2 times.
✓ Branch 6 taken 11 times.
✓ Branch 7 taken 1 time.
✓ Branch 9 taken 1 time.
✓ Branch 10 taken 10 times.
✓ Branch 11 taken 82 times.
✓ Branch 12 taken 10 times.
125 operation == ">=" || operation == "like" || operation == "not_like") {
999 82 return 30;
1000 }
1001
1002
6/6
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 5 times.
✓ Branch 4 taken 1 time.
✓ Branch 5 taken 4 times.
✓ Branch 6 taken 6 times.
✓ Branch 7 taken 4 times.
10 if (operation == "+" || operation == "-") return 40;
1003
4/6
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 3 times.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
4 if (operation == "*" || operation == "/") return 50;
1004 ✗ throw render_error{"unknown binary expression operation"};
1005 }
1006
1007 118 static std::string binary_operator(const std::string& operation)
1008 {
1009
3/3
✓ Branch 1 taken 17 times.
✓ Branch 2 taken 101 times.
✓ Branch 4 taken 17 times.
152 if (operation == "and") return "AND";
1010
3/3
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 92 times.
✓ Branch 4 taken 9 times.
119 if (operation == "or") return "OR";
1011
3/3
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 91 times.
✓ Branch 4 taken 1 time.
94 if (operation == "!=") return "<>";
1012
3/3
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 90 times.
✓ Branch 4 taken 1 time.
93 if (operation == "like") return "LIKE";
1013
3/3
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 89 times.
✓ Branch 4 taken 1 time.
92 if (operation == "not_like") return "NOT LIKE";
1014
8/8
✓ Branch 2 taken 28 times.
✓ Branch 3 taken 2 times.
✓ Branch 5 taken 27 times.
✓ Branch 6 taken 1 time.
✓ Branch 8 taken 12 times.
✓ Branch 9 taken 15 times.
✓ Branch 11 taken 10 times.
✓ Branch 12 taken 2 times.
119 if (operation == "=" || operation == "<" || operation == "<=" || operation == ">" || operation == ">=" ||
1015
10/12
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 59 times.
✓ Branch 3 taken 5 times.
✓ Branch 4 taken 5 times.
✓ Branch 6 taken 4 times.
✓ Branch 7 taken 1 time.
✓ Branch 9 taken 1 time.
✓ Branch 10 taken 3 times.
✓ Branch 12 taken 1 time.
✗ Branch 13 not taken.
✓ Branch 14 taken 89 times.
✗ Branch 15 not taken.
119 operation == "+" || operation == "-" || operation == "*" || operation == "/") {
1016 89 return operation;
1017 }
1018
1019 ✗ throw render_error{"unknown binary expression operation"};
1020 }
1021
1022 278 std::string render_column(const detail::expression_node& value) const
1023 {
1024
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 278 times.
✗ Branch 5 not taken.
278 if (value.text.empty()) throw render_error{"a column requires a name"};
1025
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 278 times.
278 if (value.qualifier.empty()) return quote_identifier(value.text);
1026
5/6
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 277 times.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 time.
✓ Branch 7 taken 277 times.
278 if (!m_merge_target_alias.empty() && value.qualifier == m_merge_target_qualifier)
1027
4/4
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
✓ Branch 7 taken 1 time.
✓ Branch 10 taken 1 time.
1 return quote_identifier(m_merge_target_alias) + "." + quote_identifier(value.text);
1028 277 return render_qualified_identifier(value.qualifier, value.text);
1029 }
1030
1031 20 std::string render_inserted_column(const detail::expression_node& value) const
1032 {
1033
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 19 times.
20 if (!m_rendering_conflict_assignments)
1034
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"inserted column references are only valid in INSERT conflict assignments"};
1035
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 19 times.
✗ Branch 5 not taken.
19 if (value.text.empty()) throw render_error{"an inserted column reference requires a name"};
1036
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 18 times.
19 if (value.qualifier != m_inserted_target_qualifier)
1037
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"inserted values must reference columns of the inserted table"};
1038
1039
4/5
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 7 times.
✓ Branch 3 taken 1 time.
✗ Branch 4 not taken.
18 switch (m_options.inserted_row_references) {
1040 8 case inserted_row_reference_style::excluded_table:
1041
2/2
✓ Branch 1 taken 8 times.
✓ Branch 4 taken 8 times.
8 return "EXCLUDED." + quote_identifier(value.text);
1042 2 case inserted_row_reference_style::values_row_alias:
1043
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2 if (m_inserted_row_alias.empty())
1044 ✗ throw render_error{"the selected proposed-row reference style requires a VALUES row alias"};
1045
4/4
✓ Branch 1 taken 2 times.
✓ Branch 4 taken 2 times.
✓ Branch 7 taken 2 times.
✓ Branch 10 taken 2 times.
2 return quote_identifier(m_inserted_row_alias) + "." + quote_identifier(value.text);
1046 7 case inserted_row_reference_style::merge_source:
1047
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✗ Branch 5 not taken.
7 if (m_inserted_row_alias.empty()) throw render_error{"a generated MERGE source alias is unavailable"};
1048
4/4
✓ Branch 1 taken 7 times.
✓ Branch 4 taken 7 times.
✓ Branch 7 taken 7 times.
✓ Branch 10 taken 7 times.
7 return quote_identifier(m_inserted_row_alias) + "." + quote_identifier(value.text);
1049 1 case inserted_row_reference_style::unsupported:
1050
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"inserted column references are not supported by the selected SQL options"};
1051 }
1052 ✗ throw render_error{"unknown proposed-row reference style"};
1053 }
1054
1055 121 std::string render_relation(const detail::relation_ptr& value)
1056 {
1057
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 121 times.
✗ Branch 5 not taken.
121 if (!value) throw render_error{"cannot render an empty relation node"};
1058
3/3
✓ Branch 1 taken 16 times.
✓ Branch 2 taken 105 times.
✓ Branch 5 taken 14 times.
121 if (value->kind == detail::relation_kind::join) return render_join(*value);
1059
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 103 times.
105 if (value->kind == detail::relation_kind::subquery) {
1060
1/3
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 6 not taken.
2 if (!value->subquery) throw render_error{"a derived relation requires a query"};
1061
1/3
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 6 not taken.
2 if (value->alias.empty()) throw render_error{"a derived relation requires an alias"};
1062
5/5
✓ Branch 2 taken 2 times.
✓ Branch 7 taken 2 times.
✓ Branch 10 taken 2 times.
✓ Branch 13 taken 2 times.
✓ Branch 16 taken 2 times.
2 return "(" + render_query_node(*value->subquery) + ")" + render_relation_alias(value->alias);
1063 }
1064
1065
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 99 times.
103 if (value->kind == detail::relation_kind::cte) {
1066
1/3
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
✗ Branch 6 not taken.
4 if (value->name.empty()) throw render_error{"a CTE relation requires a name"};
1067
1/1
✓ Branch 2 taken 4 times.
4 std::string sql = quote_identifier(value->name);
1068
1/4
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
✗ Branch 6 not taken.
✗ Branch 9 not taken.
4 if (!value->alias.empty()) sql += render_relation_alias(value->alias);
1069 4 return sql;
1070 4 }
1071
1072
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 99 times.
✗ Branch 5 not taken.
99 if (value->kind != detail::relation_kind::table) throw render_error{"this relation kind is not implemented yet"};
1073
1/3
✗ Branch 2 not taken.
✓ Branch 3 taken 99 times.
✗ Branch 6 not taken.
99 if (value->name.empty()) throw render_error{"a table requires a name"};
1074
1075
1/1
✓ Branch 3 taken 99 times.
99 std::string sql = render_qualified_identifier(value->qualifiers, value->name);
1076
4/4
✓ Branch 2 taken 20 times.
✓ Branch 3 taken 79 times.
✓ Branch 6 taken 20 times.
✓ Branch 9 taken 20 times.
99 if (!value->alias.empty()) sql += render_relation_alias(value->alias);
1077 99 return sql;
1078 99 }
1079
1080 16 std::string render_join(const detail::relation_node& value)
1081 {
1082
3/7
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 16 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 16 times.
✗ Branch 10 not taken.
16 if (!value.left || !value.right) throw render_error{"a join requires left and right relations"};
1083
4/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 13 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 15 times.
19 if (value.join_type == detail::join_kind::right &&
1084
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 2 times.
3 !has_capability(m_options.capabilities, sql_capability::right_join)) {
1085
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"RIGHT JOIN is not supported by the selected SQL options"};
1086 }
1087
4/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 14 times.
18 if (value.join_type == detail::join_kind::full &&
1088
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 2 times.
3 !has_capability(m_options.capabilities, sql_capability::full_join))
1089
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"FULL JOIN is not supported by the selected SQL options"};
1090
1091 14 std::string sql = render_relation(value.left);
1092
4/4
✓ Branch 1 taken 14 times.
✓ Branch 4 taken 14 times.
✓ Branch 7 taken 14 times.
✓ Branch 10 taken 14 times.
14 sql += " " + join_keyword(value.join_type) + " ";
1093
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 13 times.
14 if (value.right->kind == detail::relation_kind::join)
1094
4/4
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
✓ Branch 7 taken 1 time.
✓ Branch 10 taken 1 time.
1 sql += "(" + render_relation(value.right) + ")";
1095 else
1096
2/2
✓ Branch 1 taken 13 times.
✓ Branch 4 taken 13 times.
13 sql += render_relation(value.right);
1097
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 11 times.
14 if (value.join_type == detail::join_kind::cross) {
1098
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 5 not taken.
3 if (value.condition) throw render_error{"CROSS JOIN cannot have an ON condition"};
1099 3 return sql;
1100 }
1101
1102
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
✗ Branch 5 not taken.
11 if (!value.condition) throw render_error{"this join requires an ON condition"};
1103
1/1
✓ Branch 1 taken 11 times.
11 sql += " ON ";
1104
2/2
✓ Branch 1 taken 11 times.
✓ Branch 4 taken 11 times.
11 sql += render_expression(value.condition);
1105 11 return sql;
1106 ✗ }
1107
1108 14 static std::string join_keyword(detail::join_kind kind)
1109 {
1110
5/6
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
14 switch (kind) {
1111 4 case detail::join_kind::inner:
1112
1/1
✓ Branch 1 taken 4 times.
8 return "INNER JOIN";
1113 3 case detail::join_kind::left:
1114
1/1
✓ Branch 1 taken 3 times.
6 return "LEFT JOIN";
1115 2 case detail::join_kind::right:
1116
1/1
✓ Branch 1 taken 2 times.
4 return "RIGHT JOIN";
1117 2 case detail::join_kind::full:
1118
1/1
✓ Branch 1 taken 2 times.
4 return "FULL JOIN";
1119 3 case detail::join_kind::cross:
1120
1/1
✓ Branch 1 taken 3 times.
6 return "CROSS JOIN";
1121 }
1122 ✗ throw render_error{"unknown join kind"};
1123 }
1124
1125 1053 std::string quote_identifier(const std::string& identifier) const
1126 {
1127
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 1053 times.
✗ Branch 5 not taken.
1053 if (identifier.empty()) throw render_error{"cannot quote an empty identifier"};
1128
2/2
✓ Branch 0 taken 77 times.
✓ Branch 1 taken 976 times.
1053 if (m_options.identifier_quotes == identifier_quote_style::brackets) {
1129
1/1
✓ Branch 1 taken 77 times.
77 std::string result{"["};
1130
2/2
✓ Branch 5 taken 513 times.
✓ Branch 6 taken 77 times.
590 for (const char character : identifier) {
1131
1/1
✓ Branch 1 taken 513 times.
513 result += character;
1132
1/3
✗ Branch 0 not taken.
✓ Branch 1 taken 513 times.
✗ Branch 3 not taken.
513 if (character == ']') result += ']';
1133 }
1134
1/1
✓ Branch 1 taken 77 times.
77 return result + "]";
1135 77 }
1136
1137
2/2
✓ Branch 0 taken 76 times.
✓ Branch 1 taken 900 times.
976 const char quote = m_options.identifier_quotes == identifier_quote_style::backtick ? '`' : '"';
1138
1/1
✓ Branch 1 taken 976 times.
976 std::string result(1, quote);
1139
2/2
✓ Branch 5 taken 4731 times.
✓ Branch 6 taken 976 times.
5707 for (const char character : identifier) {
1140
1/1
✓ Branch 1 taken 4731 times.
4731 result += character;
1141
3/3
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 4728 times.
✓ Branch 3 taken 3 times.
4731 if (character == quote) result += quote;
1142 }
1143
1/1
✓ Branch 1 taken 976 times.
976 result += quote;
1144 976 return result;
1145 976 }
1146
1147 13 static std::string render_unquoted_identifier(const std::string& identifier)
1148 {
1149
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 5 not taken.
13 if (identifier.empty()) throw render_error{"cannot render an empty unquoted identifier"};
1150
2/2
✓ Branch 5 taken 71 times.
✓ Branch 6 taken 12 times.
83 for (const char character : identifier) {
1151 71 const unsigned char value = static_cast<unsigned char>(character);
1152
7/8
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 68 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 1 time.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 1 time.
✗ Branch 7 not taken.
71 if (!std::isalnum(value) && character != '_' && character != '$' && character != '#')
1153
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"an unquoted identifier contains an unsupported character"};
1154 }
1155 12 return identifier;
1156 }
1157
1158 36 std::string render_relation_alias(const std::string& alias) const
1159 {
1160
3/3
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 6 times.
✓ Branch 3 taken 36 times.
36 const std::string prefix = m_options.relation_aliases == relation_alias_style::with_as ? " AS " : " ";
1161
2/2
✓ Branch 1 taken 36 times.
✓ Branch 4 taken 36 times.
72 return prefix + quote_identifier(alias);
1162 36 }
1163
1164 454 std::string render_qualified_identifier(const std::vector<std::string>& qualifiers,
1165 const std::string& identifier) const
1166 {
1167 454 std::string sql;
1168
2/2
✓ Branch 4 taken 288 times.
✓ Branch 5 taken 454 times.
742 for (const std::string& qualifier : qualifiers) {
1169
3/3
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 284 times.
✓ Branch 4 taken 4 times.
288 if (!sql.empty()) sql += ".";
1170
2/2
✓ Branch 1 taken 288 times.
✓ Branch 4 taken 288 times.
288 sql += quote_identifier(qualifier);
1171 }
1172
3/3
✓ Branch 1 taken 284 times.
✓ Branch 2 taken 170 times.
✓ Branch 4 taken 284 times.
454 if (!sql.empty()) sql += ".";
1173
2/2
✓ Branch 1 taken 454 times.
✓ Branch 4 taken 454 times.
908 return sql + quote_identifier(identifier);
1174 454 }
1175
1176 15 static bool contains_inserted_row(const detail::expression_ptr& value)
1177 {
1178
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
15 if (!value) return false;
1179
2/2
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 6 times.
15 if (value->kind == detail::expression_kind::inserted_column) return true;
1180
1/2
✗ Branch 6 not taken.
✓ Branch 7 taken 6 times.
6 for (const detail::expression_ptr& operand : value->operands)
1181 ✗ if (contains_inserted_row(operand)) return true;
1182 6 return false;
1183 }
1184
1185 45 static std::vector<std::string> relation_qualifier(const detail::relation_ptr& value)
1186 {
1187
3/6
✓ Branch 1 taken 45 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 45 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 45 times.
45 if (!value || value->kind != detail::relation_kind::table) return {};
1188
1/7
✗ Branch 2 not taken.
✓ Branch 3 taken 45 times.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
45 if (!value->alias.empty()) return {value->alias};
1189
1190
1/1
✓ Branch 2 taken 45 times.
45 std::vector<std::string> qualifier = value->qualifiers;
1191
1/1
✓ Branch 2 taken 45 times.
45 qualifier.push_back(value->name);
1192 45 return qualifier;
1193 45 }
1194
1195 4 static std::string make_inserted_row_alias(const detail::relation_ptr& target)
1196 {
1197
1/1
✓ Branch 1 taken 4 times.
4 std::string alias{"_sqlon_inserted"};
1198
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
4 if (!target) return alias;
1199
2/2
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 4 times.
5 while (equal_ascii_case_insensitive(alias, target->name))
1200
1/1
✓ Branch 1 taken 1 time.
1 alias += "_";
1201 4 return alias;
1202 ✗ }
1203
1204 5 static bool equal_ascii_case_insensitive(const std::string& left, const std::string& right)
1205 {
1206
2/2
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 1 time.
5 if (left.size() != right.size()) return false;
1207
2/2
✓ Branch 1 taken 15 times.
✓ Branch 2 taken 1 time.
16 for (std::size_t index = 0; index < left.size(); ++index) {
1208 15 const unsigned char left_character = static_cast<unsigned char>(left[index]);
1209 15 const unsigned char right_character = static_cast<unsigned char>(right[index]);
1210
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 if (std::tolower(left_character) != std::tolower(right_character)) return false;
1211 }
1212 1 return true;
1213 }
1214
1215 49 static bool conflict_uses_inserted_row(const detail::conflict_clause_node& value)
1216 {
1217
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 45 times.
49 if (value.action == detail::conflict_clause_node::kind::update_inserted_values) return true;
1218
2/2
✓ Branch 5 taken 15 times.
✓ Branch 6 taken 36 times.
51 for (const detail::assignment_node& assignment : value.assignments)
1219
3/3
✓ Branch 1 taken 15 times.
✓ Branch 3 taken 9 times.
✓ Branch 4 taken 6 times.
15 if (contains_inserted_row(assignment.value)) return true;
1220 36 return false;
1221 }
1222
1223 270 std::string render_parameter(const detail::expression_node& value)
1224 {
1225
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 269 times.
270 if (m_mode == renderer_mode::prepared)
1226
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"a prepared query definition must use parameter_slot values instead of bound values"};
1227
1228
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 269 times.
✗ Branch 5 not taken.
269 if (!value.bound_parameter) throw render_error{"a bound parameter has no value"};
1229
1230
6/6
✓ Branch 1 taken 25 times.
✓ Branch 2 taken 244 times.
✓ Branch 4 taken 20 times.
✓ Branch 5 taken 5 times.
✓ Branch 6 taken 20 times.
✓ Branch 7 taken 249 times.
269 if (!value.text.empty() && supports_parameter_reuse()) {
1231
1/1
✓ Branch 1 taken 20 times.
20 const auto existing = m_named_parameter_positions.find(value.text);
1232
2/2
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 14 times.
20 if (existing != m_named_parameter_positions.end()) {
1233 6 const parameter_storage& existing_value = m_result.parameters[existing->second].value.value();
1234
3/3
✓ Branch 3 taken 6 times.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 5 times.
6 if (existing_value != value.bound_parameter->value())
1235
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"a named parameter is used with conflicting values"};
1236
1237
1/1
✓ Branch 4 taken 5 times.
5 return *m_result.parameters[existing->second].placeholder;
1238 }
1239 }
1240
1241 263 const std::size_t position = m_result.parameters.size();
1242
1/1
✓ Branch 1 taken 260 times.
263 std::string rendered_placeholder = placeholder(position, value.text);
1243
2/2
✓ Branch 1 taken 242 times.
✓ Branch 2 taken 18 times.
520 m_result.parameters.push_back({value.text.empty() ? std::nullopt : std::optional<std::string>{value.text},
1244 *value.bound_parameter, rendered_placeholder});
1245
7/7
✓ Branch 1 taken 18 times.
✓ Branch 2 taken 242 times.
✓ Branch 4 taken 13 times.
✓ Branch 5 taken 5 times.
✓ Branch 6 taken 13 times.
✓ Branch 7 taken 247 times.
✓ Branch 9 taken 13 times.
260 if (!value.text.empty() && supports_parameter_reuse()) m_named_parameter_positions.emplace(value.text, position);
1246
1247 260 return rendered_placeholder;
1248 520 }
1249
1250 34 std::string render_parameter_slot(const detail::expression_node& value)
1251 {
1252
1/3
✗ Branch 0 not taken.
✓ Branch 1 taken 34 times.
✗ Branch 4 not taken.
34 if (m_mode != renderer_mode::prepared) throw render_error{"an unbound parameter slot requires prepare()"};
1253
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 34 times.
✗ Branch 5 not taken.
34 if (!value.parameter_slot) throw render_error{"a prepared parameter slot has no metadata"};
1254
1255 34 const detail::parameter_slot_node& slot = *value.parameter_slot;
1256
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 34 times.
✗ Branch 5 not taken.
34 if (slot.name.empty()) throw render_error{"a prepared parameter requires a non-empty name"};
1257
1258 34 std::size_t logical_index{};
1259 const std::unordered_map<std::size_t, std::size_t>::const_iterator existing_slot =
1260
1/1
✓ Branch 1 taken 34 times.
34 m_prepared_parameter_indices.find(slot.identity);
1261
2/2
✓ Branch 2 taken 29 times.
✓ Branch 3 taken 5 times.
34 if (existing_slot == m_prepared_parameter_indices.end()) {
1262 const std::unordered_map<std::string, std::size_t>::const_iterator existing_name =
1263
1/1
✓ Branch 1 taken 29 times.
29 m_prepared_parameter_names.find(slot.name);
1264
2/2
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 28 times.
29 if (existing_name != m_prepared_parameter_names.end())
1265
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"prepared parameter names must be unique"};
1266
1267 28 logical_index = m_prepared_parameters.size();
1268 28 m_prepared_parameters.push_back({slot.identity, slot.name, slot.kind, slot.nullable, slot.custom_type_id});
1269
1/1
✓ Branch 1 taken 28 times.
28 m_prepared_parameter_indices.emplace(slot.identity, logical_index);
1270
1/1
✓ Branch 1 taken 28 times.
28 m_prepared_parameter_names.emplace(slot.name, logical_index);
1271 } else {
1272 5 logical_index = existing_slot->second;
1273 5 const prepared_parameter& existing = m_prepared_parameters[logical_index];
1274 5 const bool custom_types_match = existing.custom_type_id == slot.custom_type_id;
1275
4/8
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 5 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 5 times.
10 if (existing.name != slot.name || existing.kind != slot.kind || existing.nullable != slot.nullable ||
1276
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 !custom_types_match)
1277 ✗ throw render_error{"a prepared parameter identity has conflicting metadata"};
1278 }
1279
1280
2/2
✓ Branch 1 taken 29 times.
✓ Branch 2 taken 4 times.
33 if (supports_parameter_reuse()) {
1281 const std::unordered_map<std::size_t, std::size_t>::const_iterator existing_position =
1282
1/1
✓ Branch 1 taken 29 times.
29 m_prepared_parameter_positions.find(slot.identity);
1283
2/2
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 26 times.
29 if (existing_position != m_prepared_parameter_positions.end())
1284
1/1
✓ Branch 4 taken 3 times.
3 return *m_prepared_bindings[existing_position->second].placeholder;
1285 }
1286
1287 30 const std::size_t binding_position = m_prepared_bindings.size();
1288
1/1
✓ Branch 1 taken 30 times.
30 std::string rendered_placeholder = placeholder(binding_position, slot.name);
1289
1/1
✓ Branch 1 taken 30 times.
30 m_prepared_bindings.push_back({logical_index, rendered_placeholder});
1290
3/3
✓ Branch 1 taken 26 times.
✓ Branch 2 taken 4 times.
✓ Branch 4 taken 26 times.
30 if (supports_parameter_reuse()) m_prepared_parameter_positions.emplace(slot.identity, binding_position);
1291 30 return rendered_placeholder;
1292 88 }
1293
1294 293 std::string placeholder(std::size_t position, const std::string& logical_name)
1295 {
1296
4/4
✓ Branch 0 taken 41 times.
✓ Branch 1 taken 187 times.
✓ Branch 2 taken 64 times.
✓ Branch 3 taken 1 time.
293 switch (m_options.placeholders) {
1297 41 case placeholder_style::question_mark:
1298
1/1
✓ Branch 1 taken 41 times.
82 return "?";
1299 187 case placeholder_style::numbered:
1300
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 186 times.
187 if (m_options.numbered_parameter_prefix.empty())
1301
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"a numbered parameter prefix cannot be empty"};
1302
2/2
✓ Branch 1 taken 186 times.
✓ Branch 4 taken 186 times.
186 return m_options.numbered_parameter_prefix + std::to_string(position + 1);
1303 64 case placeholder_style::named: {
1304
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 64 times.
✗ Branch 5 not taken.
64 if (m_options.named_parameter_prefix.empty()) throw render_error{"a named parameter prefix cannot be empty"};
1305
2/2
✓ Branch 1 taken 16 times.
✓ Branch 2 taken 48 times.
64 if (!logical_name.empty()) {
1306
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 15 times.
16 if (!valid_named_placeholder(logical_name))
1307
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"a named placeholder requires an identifier-like parameter name"};
1308
3/3
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 3 times.
✓ Branch 4 taken 12 times.
15 if (supports_parameter_reuse()) return m_options.named_parameter_prefix + logical_name;
1309
3/3
✓ Branch 1 taken 3 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 1 time.
3 if (m_emitted_parameter_names.insert(logical_name).second)
1310
1/1
✓ Branch 1 taken 2 times.
2 return m_options.named_parameter_prefix + logical_name;
1311
1312 1 std::size_t suffix = position + 1;
1313 1 std::string emitted_name;
1314 do
1315
3/3
✓ Branch 1 taken 2 times.
✓ Branch 4 taken 2 times.
✓ Branch 7 taken 2 times.
2 emitted_name = logical_name + "_" + std::to_string(suffix++);
1316
5/5
✓ Branch 1 taken 2 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 1 time.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 1 time.
3 while (m_reserved_parameter_names.count(emitted_name) != 0U ||
1317
2/3
✓ Branch 1 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
1 !m_emitted_parameter_names.insert(emitted_name).second);
1318
1/1
✓ Branch 1 taken 1 time.
1 return m_options.named_parameter_prefix + emitted_name;
1319 1 }
1320
1321 48 std::size_t suffix = position + 1;
1322 48 std::string generated_name;
1323 do
1324
2/2
✓ Branch 1 taken 49 times.
✓ Branch 4 taken 49 times.
49 generated_name = "p" + std::to_string(suffix++);
1325
3/3
✓ Branch 1 taken 49 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 48 times.
49 while (!m_reserved_parameter_names.insert(generated_name).second);
1326
1/1
✓ Branch 1 taken 48 times.
48 return m_options.named_parameter_prefix + generated_name;
1327 48 }
1328 }
1329
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"unknown placeholder style"};
1330 }
1331
1332 16 static bool valid_named_placeholder(const std::string& value)
1333 {
1334
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
16 if (value.empty()) return false;
1335 16 const unsigned char first = static_cast<unsigned char>(value.front());
1336
2/6
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 16 times.
16 if (std::isalpha(first) == 0 && value.front() != '_') return false;
1337
2/2
✓ Branch 5 taken 118 times.
✓ Branch 6 taken 15 times.
133 for (const char character : value) {
1338 118 const unsigned char byte = static_cast<unsigned char>(character);
1339
4/4
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 108 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 9 times.
118 if (std::isalnum(byte) == 0 && character != '_') return false;
1340 }
1341 15 return true;
1342 }
1343
1344 294 void collect_parameter_names(const detail::query_node& value)
1345 {
1346
2/2
✓ Branch 5 taken 276 times.
✓ Branch 6 taken 294 times.
570 for (const detail::expression_ptr& expression : value.projection)
1347
1/1
✓ Branch 1 taken 276 times.
276 collect_parameter_names(expression);
1348 294 collect_parameter_names(value.from);
1349 294 collect_parameter_names(value.where);
1350
2/2
✓ Branch 5 taken 7 times.
✓ Branch 6 taken 294 times.
301 for (const detail::expression_ptr& expression : value.group_by)
1351
1/1
✓ Branch 1 taken 7 times.
7 collect_parameter_names(expression);
1352 294 collect_parameter_names(value.having);
1353
2/2
✓ Branch 5 taken 17 times.
✓ Branch 6 taken 294 times.
311 for (const detail::order_by_node& order : value.order_by)
1354
1/1
✓ Branch 1 taken 17 times.
17 collect_parameter_names(order.expression);
1355
2/2
✓ Branch 5 taken 18 times.
✓ Branch 6 taken 294 times.
312 for (const detail::set_operation_node& operation : value.set_operations)
1356
2/3
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 18 times.
18 if (operation.query) collect_parameter_names(*operation.query);
1357
2/2
✓ Branch 5 taken 15 times.
✓ Branch 6 taken 294 times.
309 for (const detail::common_table_expression_node& cte : value.ctes)
1358
2/3
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 15 times.
15 if (cte.query) collect_parameter_names(*cte.query);
1359
2/2
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 289 times.
294 if (value.insert_source) collect_parameter_names(*value.insert_source);
1360
2/2
✓ Branch 5 taken 65 times.
✓ Branch 6 taken 294 times.
359 for (const std::vector<detail::expression_ptr>& row : value.values)
1361
2/2
✓ Branch 5 taken 124 times.
✓ Branch 6 taken 65 times.
189 for (const detail::expression_ptr& expression : row)
1362
1/1
✓ Branch 1 taken 124 times.
124 collect_parameter_names(expression);
1363
2/2
✓ Branch 5 taken 13 times.
✓ Branch 6 taken 294 times.
307 for (const detail::assignment_node& assignment : value.assignments)
1364
1/1
✓ Branch 1 taken 13 times.
13 collect_parameter_names(assignment.value);
1365
2/2
✓ Branch 5 taken 21 times.
✓ Branch 6 taken 294 times.
315 for (const detail::assignment_node& assignment : value.conflict.assignments)
1366
1/1
✓ Branch 1 taken 21 times.
21 collect_parameter_names(assignment.value);
1367
2/2
✓ Branch 5 taken 13 times.
✓ Branch 6 taken 294 times.
307 for (const detail::expression_ptr& expression : value.returning)
1368
1/1
✓ Branch 1 taken 13 times.
13 collect_parameter_names(expression);
1369 294 }
1370
1371 562 void collect_parameter_names(const detail::relation_ptr& value)
1372 {
1373
2/2
✓ Branch 1 taken 428 times.
✓ Branch 2 taken 134 times.
562 if (!value) return;
1374 134 collect_parameter_names(value->left);
1375 134 collect_parameter_names(value->right);
1376 134 collect_parameter_names(value->condition);
1377
2/2
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 132 times.
134 if (value->subquery) collect_parameter_names(*value->subquery);
1378 }
1379
1380 1605 void collect_parameter_names(const detail::expression_ptr& value)
1381 {
1382
2/2
✓ Branch 1 taken 649 times.
✓ Branch 2 taken 956 times.
1605 if (!value) return;
1383
6/6
✓ Branch 1 taken 653 times.
✓ Branch 2 taken 303 times.
✓ Branch 4 taken 34 times.
✓ Branch 5 taken 619 times.
✓ Branch 6 taken 25 times.
✓ Branch 7 taken 931 times.
1293 if ((value->kind == detail::expression_kind::parameter || value->kind == detail::expression_kind::parameter_slot) &&
1384
2/2
✓ Branch 2 taken 25 times.
✓ Branch 3 taken 312 times.
337 !value->text.empty()) {
1385 25 m_reserved_parameter_names.insert(value->text);
1386 }
1387
2/2
✓ Branch 6 taken 404 times.
✓ Branch 7 taken 956 times.
1360 for (const detail::expression_ptr& operand : value->operands)
1388
1/1
✓ Branch 1 taken 404 times.
404 collect_parameter_names(operand);
1389
2/2
✓ Branch 2 taken 17 times.
✓ Branch 3 taken 939 times.
956 if (value->window) {
1390
2/2
✓ Branch 7 taken 4 times.
✓ Branch 8 taken 17 times.
21 for (const detail::expression_ptr& expression : value->window->partition_by)
1391
1/1
✓ Branch 1 taken 4 times.
4 collect_parameter_names(expression);
1392
2/2
✓ Branch 7 taken 4 times.
✓ Branch 8 taken 17 times.
21 for (const detail::order_by_node& order : value->window->order_by)
1393
1/1
✓ Branch 1 taken 4 times.
4 collect_parameter_names(order.expression);
1394 }
1395
2/2
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 948 times.
956 if (value->subquery) collect_parameter_names(*value->subquery);
1396 }
1397
1398 121 bool supports_parameter_reuse() const noexcept
1399 {
1400
3/4
✓ Branch 0 taken 61 times.
✓ Branch 1 taken 48 times.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
121 switch (m_options.placeholders) {
1401 61 case placeholder_style::numbered:
1402 61 return has_capability(m_options.capabilities, sql_capability::reusable_numbered_parameters);
1403 48 case placeholder_style::named:
1404 48 return has_capability(m_options.capabilities, sql_capability::reusable_named_parameters);
1405 12 case placeholder_style::question_mark:
1406 12 return false;
1407 }
1408 ✗ return false;
1409 }
1410
1411 41 std::string render_literal(const detail::expression_node& value) const
1412 {
1413
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 41 times.
✗ Branch 5 not taken.
41 if (!value.bound_parameter) throw render_error{"a SQL literal has no value"};
1414
1415 41 return value.bound_parameter->visit_storage([this](const auto& literal_value) -> std::string {
1416 using value_type = std::decay_t<decltype(literal_value)>;
1417 if constexpr (std::is_same_v<value_type, std::nullptr_t>)
1418
1/1
✓ Branch 1 taken 1 time.
2 return "NULL";
1419 else if constexpr (std::is_same_v<value_type, bool>)
1420 2 return m_options.boolean_literals == boolean_literal_style::integers ? (literal_value ? "1" : "0")
1421
5/7
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
4 : (literal_value ? "TRUE" : "FALSE");
1422 else if constexpr (std::is_integral_v<value_type>)
1423 28 return std::to_string(literal_value);
1424 else if constexpr (std::is_floating_point_v<value_type>) {
1425
3/3
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✓ Branch 5 taken 1 time.
2 if (!std::isfinite(literal_value)) throw render_error{"non-finite SQL literals are not supported"};
1426
1/1
✓ Branch 1 taken 1 time.
1 std::ostringstream stream;
1427
2/2
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
1 stream.imbue(std::locale::classic());
1428
1/1
✓ Branch 3 taken 1 time.
1 stream << std::setprecision(std::numeric_limits<value_type>::max_digits10) << literal_value;
1429
1/1
✓ Branch 1 taken 1 time.
2 return stream.str();
1430 1 } else if constexpr (std::is_same_v<value_type, std::string>) {
1431
1/1
✓ Branch 1 taken 6 times.
6 std::string result{"'"};
1432
2/2
✓ Branch 5 taken 36 times.
✓ Branch 6 taken 4 times.
40 for (const char character : literal_value) {
1433
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 23 times.
36 if (m_options.string_literals == string_literal_style::backslash_escaped) {
1434
9/9
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 1 time.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 1 time.
✓ Branch 7 taken 1 time.
✓ Branch 8 taken 5 times.
13 switch (character) {
1435 1 case '\0':
1436
1/1
✓ Branch 1 taken 1 time.
1 result += "\\0";
1437 1 break;
1438 1 case '\b':
1439
1/1
✓ Branch 1 taken 1 time.
1 result += "\\b";
1440 1 break;
1441 1 case '\n':
1442
1/1
✓ Branch 1 taken 1 time.
1 result += "\\n";
1443 1 break;
1444 1 case '\r':
1445
1/1
✓ Branch 1 taken 1 time.
1 result += "\\r";
1446 1 break;
1447 1 case '\t':
1448
1/1
✓ Branch 1 taken 1 time.
1 result += "\\t";
1449 1 break;
1450 1 case '\x1a':
1451
1/1
✓ Branch 1 taken 1 time.
1 result += "\\Z";
1452 1 break;
1453 1 case '\\':
1454
1/1
✓ Branch 1 taken 1 time.
1 result += "\\\\";
1455 1 break;
1456 1 case '\'':
1457
1/1
✓ Branch 1 taken 1 time.
1 result += "\\'";
1458 1 break;
1459 5 default:
1460
1/1
✓ Branch 1 taken 5 times.
5 result += character;
1461 5 break;
1462 }
1463 } else {
1464
3/3
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 22 times.
✓ Branch 4 taken 1 time.
23 if (character == '\0') throw render_error{"NUL bytes are not supported in standard SQL literals"};
1465
3/4
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 21 times.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
22 if (character == '\\' && m_options.string_literals == string_literal_style::mode_independent) {
1466
1/1
✓ Branch 2 taken 1 time.
1 throw render_error{"backslashes in mode-independent SQL literals require a bound parameter"};
1467 }
1468
1/1
✓ Branch 1 taken 21 times.
21 result += character;
1469
3/3
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 19 times.
✓ Branch 3 taken 2 times.
21 if (character == '\'') result += '\'';
1470 }
1471 }
1472
1/1
✓ Branch 1 taken 4 times.
4 result += '\'';
1473 8 return result;
1474 6 } else
1475
2/2
std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > sqlon::(anonymous namespace)::renderer::render_literal(sqlon::detail::expression_node const&) const::{lambda(auto:1 const&)#1}::operator()<sqlon::custom_parameter_value>(sqlon::custom_parameter_value const&) const:
✓ Branch 2 taken 1 time.
std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > sqlon::(anonymous namespace)::renderer::render_literal(sqlon::detail::expression_node const&) const::{lambda(auto:1 const&)#1}::operator()<std::vector<std::byte, std::allocator<std::byte> > >(std::vector<std::byte, std::allocator<std::byte> > const&) const:
✓ Branch 2 taken 1 time.
2 throw render_error{"binary and custom SQL literals are not supported"};
1476 });
1477 }
1478
1479 6 std::string render_keyword(const detail::expression_node& value) const
1480 {
1481
3/3
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 4 times.
✓ Branch 4 taken 2 times.
10 if (value.text == "current_timestamp") return "CURRENT_TIMESTAMP";
1482
3/3
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 1 time.
✓ Branch 4 taken 3 times.
10 if (value.text == "null") return "NULL";
1483
2/3
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
✓ Branch 5 taken 1 time.
1 if (value.text == "default") throw render_error{"DEFAULT is only valid as an INSERT or assignment value"};
1484 ✗ throw render_error{"unknown SQL keyword expression"};
1485 }
1486
1487 const sql_options& m_options;
1488 renderer_mode m_mode;
1489 bool m_rendering_conflict_assignments{};
1490 std::string m_inserted_row_alias;
1491 std::vector<std::string> m_inserted_target_qualifier;
1492 std::string m_merge_target_alias;
1493 std::vector<std::string> m_merge_target_qualifier;
1494 rendered_query m_result;
1495 std::unordered_map<std::string, std::size_t> m_named_parameter_positions;
1496 std::unordered_set<std::string> m_reserved_parameter_names;
1497 std::unordered_set<std::string> m_emitted_parameter_names;
1498 std::vector<prepared_parameter> m_prepared_parameters;
1499 std::vector<prepared_binding> m_prepared_bindings;
1500 std::unordered_map<std::size_t, std::size_t> m_prepared_parameter_indices;
1501 std::unordered_map<std::string, std::size_t> m_prepared_parameter_names;
1502 std::unordered_map<std::size_t, std::size_t> m_prepared_parameter_positions;
1503 };
1504
1505 } // namespace
1506
1507 224 rendered_query render(const query& value, const sql_options& options)
1508 {
1509
1/1
✓ Branch 2 taken 158 times.
224 return renderer{options, renderer_mode::immediate}.render_query(value);
1510 }
1511
1512 22 prepared_query prepare(const query& value, const sql_options& options)
1513 {
1514
1/1
✓ Branch 2 taken 20 times.
22 return renderer{options, renderer_mode::prepared}.render_prepared_query(value);
1515 }
1516
1517 } // namespace sqlon
1518