Branch data Line data Source code
1 : : #include "backend/WasmOperator.hpp"
2 : :
3 : : #include "backend/Interpreter.hpp"
4 : : #include "backend/WasmAlgo.hpp"
5 : : #include "backend/WasmMacro.hpp"
6 : : #include <mutable/catalog/Catalog.hpp>
7 : : #include <mutable/parse/AST.hpp>
8 : : #include <mutable/util/fn.hpp>
9 : : #include <numeric>
10 : :
11 : :
12 : : using namespace m;
13 : : using namespace m::ast;
14 : : using namespace m::storage;
15 : : using namespace m::wasm;
16 : :
17 : :
18 : : /*======================================================================================================================
19 : : * CLI arguments
20 : : *====================================================================================================================*/
21 : :
22 : : namespace {
23 : :
24 : : __attribute__((constructor(201)))
25 : 1 : static void add_wasm_operator_args()
26 : : {
27 : 1 : Catalog &C = Catalog::Get();
28 : :
29 : : /*----- Command-line arguments -----*/
30 : 1 : C.arg_parser().add<std::vector<std::string_view>>(
31 : : /* group= */ "Wasm",
32 : : /* short= */ nullptr,
33 : : /* long= */ "--scan-implementations",
34 : : /* description= */ "a comma seperated list of physical scan implementations to consider (`Scan` or `IndexScan`)",
35 : 0 : /* callback= */ [](std::vector<std::string_view> impls){
36 : 0 : options::scan_implementations = option_configs::ScanImplementation(0UL);
37 [ # # ]: 0 : for (const auto &elem : impls) {
38 [ # # ]: 0 : if (strneq(elem.data(), "Scan", elem.size()))
39 : 0 : options::scan_implementations |= option_configs::ScanImplementation::SCAN;
40 [ # # ]: 0 : else if (strneq(elem.data(), "IndexScan", elem.size()))
41 : 0 : options::scan_implementations |= option_configs::ScanImplementation::INDEX_SCAN;
42 : : else
43 : 0 : std::cerr << "warning: ignore invalid physical scan implementation " << elem << std::endl;
44 : : }
45 : 0 : }
46 : : );
47 : 1 : C.arg_parser().add<std::vector<std::string_view>>(
48 : : /* group= */ "Wasm",
49 : : /* short= */ nullptr,
50 : : /* long= */ "--grouping-implementations",
51 : : /* description= */ "a comma seperated list of physical grouping implementations to consider (`HashBased` or "
52 : : "`Ordered`)",
53 : 0 : /* callback= */ [](std::vector<std::string_view> impls){
54 : 0 : options::grouping_implementations = option_configs::GroupingImplementation(0UL);
55 [ # # ]: 0 : for (const auto &elem : impls) {
56 [ # # ]: 0 : if (strneq(elem.data(), "HashBased", elem.size()))
57 : 0 : options::grouping_implementations |= option_configs::GroupingImplementation::HASH_BASED;
58 [ # # ]: 0 : else if (strneq(elem.data(), "Ordered", elem.size()))
59 : 0 : options::grouping_implementations |= option_configs::GroupingImplementation::ORDERED;
60 : : else
61 : 0 : std::cerr << "warning: ignore invalid physical grouping implementation " << elem << std::endl;
62 : : }
63 : 0 : }
64 : : );
65 : 1 : C.arg_parser().add<std::vector<std::string_view>>(
66 : : /* group= */ "Wasm",
67 : 0 : /* short= */ nullptr,
68 : : /* long= */ "--sorting-implementations",
69 : : /* description= */ "a comma seperated list of physical sorting implementations to consider (`Quicksort` or "
70 : : "`NoOp`)",
71 : 0 : /* callback= */ [](std::vector<std::string_view> impls){
72 : 0 : options::sorting_implementations = option_configs::SortingImplementation(0UL);
73 [ # # ]: 0 : for (const auto &elem : impls) {
74 [ # # ]: 1 : if (strneq(elem.data(), "Quicksort", elem.size()))
75 : 0 : options::sorting_implementations |= option_configs::SortingImplementation::QUICKSORT;
76 [ # # ]: 0 : else if (strneq(elem.data(), "NoOp", elem.size()))
77 : 0 : options::sorting_implementations |= option_configs::SortingImplementation::NOOP;
78 : : else
79 : 0 : std::cerr << "warning: ignore invalid physical sorting implementation " << elem << std::endl;
80 : : }
81 : 0 : }
82 : : );
83 : 1 : C.arg_parser().add<std::vector<std::string_view>>(
84 : : /* group= */ "Wasm",
85 : : /* short= */ nullptr,
86 : : /* long= */ "--join-implementations",
87 : : /* description= */ "a comma seperated list of physical join implementations to consider (`NestedLoops`, "
88 : : "`SimpleHash`, or `SortMerge`)",
89 : 0 : /* callback= */ [](std::vector<std::string_view> impls){
90 : 0 : options::join_implementations = option_configs::JoinImplementation(0UL);
91 [ # # ]: 0 : for (const auto &elem : impls) {
92 [ # # ]: 0 : if (strneq(elem.data(), "NestedLoops", elem.size()))
93 : 0 : options::join_implementations |= option_configs::JoinImplementation::NESTED_LOOPS;
94 [ # # ]: 0 : else if (strneq(elem.data(), "SimpleHash", elem.size()))
95 : 0 : options::join_implementations |= option_configs::JoinImplementation::SIMPLE_HASH;
96 [ # # ]: 0 : else if (strneq(elem.data(), "SortMerge", elem.size()))
97 : 0 : options::join_implementations |= option_configs::JoinImplementation::SORT_MERGE;
98 : : else
99 : 0 : std::cerr << "warning: ignore invalid physical join implementation " << elem << std::endl;
100 : : }
101 : 0 : }
102 : : );
103 : 1 : C.arg_parser().add<std::vector<std::string_view>>(
104 : : /* group= */ "Wasm",
105 : : /* short= */ nullptr,
106 : : /* long= */ "--index-implementations",
107 : : /* description= */ "a comma separated list of index implementations to consider for index scans (`Array`, or"
108 : : " `Rmi`)",
109 : 0 : /* callback= */ [](std::vector<std::string_view> impls){
110 : 0 : options::index_implementations = option_configs::IndexImplementation(0UL);
111 [ # # ]: 0 : for (const auto &elem : impls) {
112 [ # # ]: 0 : if (strneq(elem.data(), "Array", elem.size()))
113 : 0 : options::index_implementations |= option_configs::IndexImplementation::ARRAY;
114 [ # # ]: 0 : else if (strneq(elem.data(), "Rmi", elem.size()))
115 : 0 : options::index_implementations |= option_configs::IndexImplementation::RMI;
116 : : else
117 : 0 : std::cerr << "warning: ignore invalid index implementation " << elem << std::endl;
118 : : }
119 : 0 : }
120 : : );
121 : 1 : C.arg_parser().add<const char*>(
122 : : /* group= */ "Wasm",
123 : : /* short= */ nullptr,
124 : : /* long= */ "--index-scan-strategy",
125 : : /* description= */ "specify the index scan strategy (`Compilation`, `Interpretation`, or `Hybrid`)",
126 : 0 : /* callback= */ [](const char *strategy){
127 [ # # ]: 0 : if (streq(strategy, "Compilation"))
128 : 0 : options::index_scan_strategy = option_configs::IndexScanStrategy::COMPILATION;
129 [ # # ]: 0 : else if (streq(strategy, "Interpretation"))
130 : 0 : options::index_scan_strategy = option_configs::IndexScanStrategy::INTERPRETATION;
131 [ # # ]: 0 : else if (streq(strategy, "Hybrid"))
132 : 0 : options::index_scan_strategy = option_configs::IndexScanStrategy::HYBRID;
133 : : else
134 : 0 : std::cerr << "warning: ignore invalid index scan strategy " << strategy << std::endl;
135 : 0 : }
136 : : );
137 : 1 : C.arg_parser().add<const char*>(
138 : : /* group= */ "Wasm",
139 : : /* short= */ nullptr,
140 : : /* long= */ "--index-scan-compilation-strategy",
141 : : /* description= */ "specify the materialization strategy for index scans (`Callback` or `ExposedMemory`)",
142 : 0 : /* callback= */ [](const char *strategy){
143 [ # # ]: 0 : if (streq(strategy, "Callback"))
144 : 0 : options::index_scan_compilation_strategy = option_configs::IndexScanCompilationStrategy::CALLBACK;
145 [ # # ]: 0 : else if (streq(strategy, "ExposedMemory"))
146 : 0 : options::index_scan_compilation_strategy = option_configs::IndexScanCompilationStrategy::EXPOSED_MEMORY;
147 : : else
148 : 0 : std::cerr << "warning: ignore invalid index scan strategy " << strategy << std::endl;
149 : 0 : }
150 : : );
151 : 1 : C.arg_parser().add<const char*>(
152 : : /* group= */ "Wasm",
153 : : /* short= */ nullptr,
154 : : /* long= */ "--index-scan-materialization-strategy",
155 : : /* description= */ "specify the materialization strategy for index scans (`Inline` or `Memory`)",
156 : 0 : /* callback= */ [](const char *strategy){
157 [ # # ]: 0 : if (streq(strategy, "Inline"))
158 : 0 : options::index_scan_materialization_strategy = option_configs::IndexScanMaterializationStrategy::INLINE;
159 [ # # ]: 0 : else if (streq(strategy, "Memory"))
160 : 0 : options::index_scan_materialization_strategy = option_configs::IndexScanMaterializationStrategy::MEMORY;
161 : : else
162 : 0 : std::cerr << "warning: ignore invalid index scan strategy " << strategy << std::endl;
163 : 0 : }
164 : : );
165 : 1 : C.arg_parser().add<const char*>(
166 : : /* group= */ "Wasm",
167 : : /* short= */ nullptr,
168 : : /* long= */ "--filter-selection-strategy",
169 : : /* description= */ "specify the selection strategy for filters (`Branching` or `Predicated`)",
170 : 0 : /* callback= */ [](const char *strategy){
171 [ # # ]: 0 : if (streq(strategy, "Branching"))
172 : 0 : options::filter_selection_strategy = option_configs::SelectionStrategy::BRANCHING;
173 [ # # ]: 0 : else if (streq(strategy, "Predicated"))
174 : 0 : options::filter_selection_strategy = option_configs::SelectionStrategy::PREDICATED;
175 : : else
176 : 0 : std::cerr << "warning: ignore invalid filter selection strategy " << strategy << std::endl;
177 : 0 : }
178 : : );
179 : 1 : C.arg_parser().add<const char*>(
180 : : /* group= */ "Wasm",
181 : : /* short= */ nullptr,
182 : : /* long= */ "--quicksort-cmp-selection-strategy",
183 : : /* description= */ "specify the selection strategy for comparisons in quicksort (`Branching` or `Predicated`)",
184 : 0 : /* callback= */ [](const char *strategy){
185 [ # # ]: 0 : if (streq(strategy, "Branching"))
186 : 0 : options::quicksort_cmp_selection_strategy = option_configs::SelectionStrategy::BRANCHING;
187 [ # # ]: 0 : else if (streq(strategy, "Predicated"))
188 : 0 : options::quicksort_cmp_selection_strategy = option_configs::SelectionStrategy::PREDICATED;
189 : : else
190 : 0 : std::cerr << "warning: ignore invalid quicksort comparison selection strategy " << strategy << std::endl;
191 : 0 : }
192 [ # # ]: 0 : );
193 : 1 : C.arg_parser().add<const char*>(
194 : : /* group= */ "Wasm",
195 : : /* short= */ nullptr,
196 : : /* long= */ "--nested-loops-join-selection-strategy",
197 : : /* description= */ "specify the selection strategy for nested-loops joins (`Branching` or `Predicated`)",
198 : 0 : /* callback= */ [](const char *strategy){
199 [ # # ]: 0 : if (streq(strategy, "Branching"))
200 [ # # ]: 0 : options::nested_loops_join_selection_strategy = option_configs::SelectionStrategy::BRANCHING;
201 [ # # ]: 0 : else if (streq(strategy, "Predicated"))
202 : 0 : options::nested_loops_join_selection_strategy = option_configs::SelectionStrategy::PREDICATED;
203 : : else
204 : 0 : std::cerr << "warning: ignore invalid nested-loops join selection strategy " << strategy << std::endl;
205 : 0 : }
206 : : );
207 : 1 : C.arg_parser().add<const char*>(
208 : : /* group= */ "Wasm",
209 : : /* short= */ nullptr,
210 : : /* long= */ "--simple-hash-join-selection-strategy",
211 : : /* description= */ "specify the selection strategy for simple hash joins (`Branching` or `Predicated`)",
212 : 0 : /* callback= */ [](const char *strategy){
213 [ # # ]: 0 : if (streq(strategy, "Branching"))
214 : 0 : options::simple_hash_join_selection_strategy = option_configs::SelectionStrategy::BRANCHING;
215 [ # # ]: 0 : else if (streq(strategy, "Predicated"))
216 : 0 : options::simple_hash_join_selection_strategy = option_configs::SelectionStrategy::PREDICATED;
217 : : else
218 : 0 : std::cerr << "warning: ignore invalid simple hash join selection strategy " << strategy << std::endl;
219 : 0 : }
220 : : );
221 : 1 : C.arg_parser().add<const char*>(
222 : : /* group= */ "Wasm",
223 : : /* short= */ nullptr,
224 : : /* long= */ "--simple-hash-join-ordering-strategy",
225 : : /* description= */ "specify the ordering strategy for simple hash joins (`BuildOnLeft` or `BuildOnRight`)",
226 : 0 : /* callback= */ [](const char *strategy){
227 [ # # ]: 0 : if (streq(strategy, "BuildOnLeft"))
228 : 0 : options::simple_hash_join_ordering_strategy = option_configs::OrderingStrategy::BUILD_ON_LEFT;
229 [ # # ]: 0 : else if (streq(strategy, "BuildOnRight"))
230 : 0 : options::simple_hash_join_ordering_strategy = option_configs::OrderingStrategy::BUILD_ON_RIGHT;
231 : : else
232 : 0 : std::cerr << "warning: ignore invalid simple hash join ordering strategy " << strategy << std::endl;
233 : 0 : }
234 : : );
235 : 1 : C.arg_parser().add<const char*>(
236 : : /* group= */ "Wasm",
237 : : /* short= */ nullptr,
238 : : /* long= */ "--sort-merge-join-selection-strategy",
239 : : /* description= */ "specify the selection strategy for sort merge joins (`Branching` or `Predicated`)",
240 : 0 : /* callback= */ [](const char *strategy){
241 [ # # ]: 0 : if (streq(strategy, "Branching"))
242 : 0 : options::sort_merge_join_selection_strategy = option_configs::SelectionStrategy::BRANCHING;
243 [ # # ]: 0 : else if (streq(strategy, "Predicated"))
244 : 0 : options::sort_merge_join_selection_strategy = option_configs::SelectionStrategy::PREDICATED;
245 : : else
246 : 0 : std::cerr << "warning: ignore invalid sort merge join selection strategy " << strategy << std::endl;
247 : 0 : }
248 : : );
249 : 1 : C.arg_parser().add<const char*>(
250 : : /* group= */ "Wasm",
251 : : /* short= */ nullptr,
252 : : /* long= */ "--sort-merge-join-cmp-selection-strategy",
253 : : /* description= */ "specify the selection strategy for comparisons while sorting in sort merge joins "
254 : : "(`Branching` or `Predicated`)",
255 : 0 : /* callback= */ [](const char *strategy){
256 [ # # ]: 0 : if (streq(strategy, "Branching"))
257 : 0 : options::sort_merge_join_cmp_selection_strategy = option_configs::SelectionStrategy::BRANCHING;
258 [ # # ]: 0 : else if (streq(strategy, "Predicated"))
259 : 0 : options::sort_merge_join_cmp_selection_strategy = option_configs::SelectionStrategy::PREDICATED;
260 : : else
261 : 0 : std::cerr << "warning: ignore invalid sort merge join comparison selection strategy " << strategy
262 : 0 : << std::endl;
263 : 0 : }
264 : : );
265 : 1 : C.arg_parser().add<const char*>(
266 : : /* group= */ "Wasm",
267 : : /* short= */ nullptr,
268 : : /* long= */ "--hash-table-implementation",
269 : : /* description= */ "specify the hash table implementation (`OpenAddressing` or `Chained`)",
270 : 0 : /* callback= */ [](const char *impl){
271 [ # # ]: 0 : if (streq(impl, "OpenAddressing"))
272 : 0 : options::hash_table_implementation = option_configs::HashTableImplementation::OPEN_ADDRESSING;
273 [ # # ]: 0 : else if (streq(impl, "Chained"))
274 : 0 : options::hash_table_implementation = option_configs::HashTableImplementation::CHAINED;
275 : : else
276 : 0 : std::cerr << "warning: ignore invalid hash table implementation " << impl << std::endl;
277 : 0 : }
278 : : );
279 : 1 : C.arg_parser().add<const char*>(
280 : : /* group= */ "Wasm",
281 : : /* short= */ nullptr,
282 : : /* long= */ "--hash-table-probing-strategy",
283 : : /* description= */ "specify the probing strategy for hash tables (`Linear` or `Quadratic`)",
284 : 0 : /* callback= */ [](const char *strategy){
285 [ # # ]: 0 : if (streq(strategy, "Linear"))
286 : 0 : options::hash_table_probing_strategy = option_configs::ProbingStrategy::LINEAR;
287 [ # # ]: 0 : else if (streq(strategy, "Quadratic"))
288 : 0 : options::hash_table_probing_strategy = option_configs::ProbingStrategy::QUADRATIC;
289 : : else
290 : 0 : std::cerr << "warning: ignore invalid hash table probing strategy " << strategy << std::endl;
291 : 0 : }
292 : : );
293 : 1 : C.arg_parser().add<const char*>(
294 : : /* group= */ "Wasm",
295 : : /* short= */ nullptr,
296 : : /* long= */ "--hash-table-storing-strategy",
297 : : /* description= */ "specify the storing strategy for hash tables (`InPlace` or `OutOfPlace`)",
298 : 0 : /* callback= */ [](const char *strategy){
299 [ # # ]: 0 : if (streq(strategy, "InPlace"))
300 : 0 : options::hash_table_storing_strategy = option_configs::StoringStrategy::IN_PLACE;
301 [ # # ]: 0 : else if (streq(strategy, "OutOfPlace"))
302 : 0 : options::hash_table_storing_strategy = option_configs::StoringStrategy::OUT_OF_PLACE;
303 : : else
304 : 0 : std::cerr << "warning: ignore invalid hash table storing strategy " << strategy << std::endl;
305 : 0 : }
306 : : );
307 : 1 : C.arg_parser().add<double>(
308 : : /* group= */ "Wasm",
309 : : /* short= */ nullptr,
310 : : /* long= */ "--hash-table-max-load-factor",
311 : : /* description= */ "specify the maximal load factor for hash tables, i.e. the load factor at which rehashing "
312 : : "should occur (must be in [1,∞) for chained and in [0.5,1) for open-addressing hash tables)",
313 : 0 : /* callback= */ [](double load_factor){
314 : 0 : options::load_factor_open_addressing = load_factor;
315 : 0 : options::load_factor_chained = load_factor;
316 : 0 : }
317 : : );
318 : 1 : C.arg_parser().add<double>(
319 : : /* group= */ "Wasm",
320 : : /* short= */ nullptr,
321 : : /* long= */ "--hash-table-initial-capacity",
322 : : /* description= */ "specify the initial capacity for hash tables",
323 : 0 : /* callback= */ [](uint32_t initial_capacity){
324 : 0 : options::hash_table_initial_capacity = initial_capacity;
325 : 0 : }
326 : : );
327 : 1 : C.arg_parser().add<bool>(
328 : : /* group= */ "Wasm",
329 : : /* short= */ nullptr,
330 : : /* long= */ "--no-hash-based-group-join",
331 : : /* description= */ "disable potential use of hash-based group-join",
332 : 0 : /* callback= */ [](bool){ options::hash_based_group_join = false; }
333 : : );
334 : 1 : C.arg_parser().add<const char*>(
335 : : /* group= */ "Wasm",
336 : : /* short= */ nullptr,
337 : : /* long= */ "--hard-pipeline-breaker-layout",
338 : : /* description= */ "specify the layout for hard pipeline breakers (`Row`, `PAX4K`, `PAX64K`, `PAX512K`, "
339 : : "`PAX4M`, or `PAX64M`)",
340 : 0 : /* callback= */ [](const char *layout){
341 [ # # ]: 0 : if (streq(layout, "Row")) {
342 : 0 : options::hard_pipeline_breaker_layout = std::make_unique<RowLayoutFactory>();
343 : 0 : } else {
344 : : PAXLayoutFactory::block_size_t size_type;
345 : : uint64_t block_size;
346 [ # # ]: 0 : if (streq(layout, "PAX4K")) {
347 : 0 : size_type = PAXLayoutFactory::NBytes;
348 : 0 : block_size = 1UL << 12;
349 [ # # ]: 0 : } else if (streq(layout, "PAX64K")) {
350 : 0 : size_type = PAXLayoutFactory::NBytes;
351 : 0 : block_size = 1UL << 16;
352 [ # # ]: 0 : } else if (streq(layout, "PAX512K")) {
353 : 0 : size_type = PAXLayoutFactory::NBytes;
354 : 0 : block_size = 1UL << 19;
355 [ # # ]: 0 : } else if (streq(layout, "PAX4M")) {
356 : 0 : size_type = PAXLayoutFactory::NBytes;
357 : 0 : block_size = 1UL << 22;
358 [ # # ]: 0 : } else if (streq(layout, "PAX64M")) {
359 : 0 : size_type = PAXLayoutFactory::NBytes;
360 : 0 : block_size = 1UL << 26;
361 [ # # ]: 0 : } else if (streq(layout, "PAX16Tup")) {
362 : 0 : size_type = PAXLayoutFactory::NTuples;
363 : 0 : block_size = 16;
364 [ # # ]: 0 : } else if (streq(layout, "PAX128Tup")) {
365 : 0 : size_type = PAXLayoutFactory::NTuples;
366 : 0 : block_size = 128;
367 [ # # ]: 0 : } else if (streq(layout, "PAX1024Tup")) {
368 : 0 : size_type = PAXLayoutFactory::NTuples;
369 : 0 : block_size = 1024;
370 : 0 : } else {
371 : 0 : std::cerr << "warning: ignore invalid layout for hard pipeline breakers " << layout << std::endl;
372 : : }
373 : 0 : options::hard_pipeline_breaker_layout = std::make_unique<PAXLayoutFactory>(size_type, block_size);
374 : : }
375 : 0 : }
376 : : );
377 : 1 : C.arg_parser().add<std::vector<std::string_view>>(
378 : : /* group= */ "Wasm",
379 : : /* short= */ nullptr,
380 : : /* long= */ "--soft-pipeline-breaker",
381 : : /* description= */ "a comma seperated list where to insert soft pipeline breakers (`AfterAll`, `AfterScan`, "
382 : : "`AfterFilter`, `AfterProjection`, `AfterNestedLoopsJoin`, or `AfterSimpleHashJoin`)",
383 : 0 : /* callback= */ [](std::vector<std::string_view> location){
384 : 0 : options::soft_pipeline_breaker = option_configs::SoftPipelineBreakerStrategy(0UL);
385 [ # # ]: 0 : for (const auto &elem : location) {
386 [ # # ]: 0 : if (strneq(elem.data(), "AfterAll", elem.size()))
387 : 0 : options::soft_pipeline_breaker |= option_configs::SoftPipelineBreakerStrategy::AFTER_ALL;
388 [ # # ]: 0 : else if (strneq(elem.data(), "AfterScan", elem.size()))
389 : 0 : options::soft_pipeline_breaker |= option_configs::SoftPipelineBreakerStrategy::AFTER_SCAN;
390 [ # # ]: 0 : else if (strneq(elem.data(), "AfterFilter", elem.size()))
391 : 0 : options::soft_pipeline_breaker |= option_configs::SoftPipelineBreakerStrategy::AFTER_FILTER;
392 [ # # ]: 0 : else if (strneq(elem.data(), "AfterIndexScan", elem.size()))
393 : 0 : options::soft_pipeline_breaker |= option_configs::SoftPipelineBreakerStrategy::AFTER_INDEX_SCAN;
394 [ # # ]: 0 : else if (strneq(elem.data(), "AfterProjection", elem.size()))
395 : 0 : options::soft_pipeline_breaker |= option_configs::SoftPipelineBreakerStrategy::AFTER_PROJECTION;
396 [ # # ]: 0 : else if (strneq(elem.data(), "AfterNestedLoopsJoin", elem.size()))
397 : 0 : options::soft_pipeline_breaker |= option_configs::SoftPipelineBreakerStrategy::AFTER_NESTED_LOOPS_JOIN;
398 [ # # ]: 0 : else if (strneq(elem.data(), "AfterSimpleHashJoin", elem.size()))
399 : 0 : options::soft_pipeline_breaker |= option_configs::SoftPipelineBreakerStrategy::AFTER_SIMPLE_HASH_JOIN;
400 : : else
401 : 0 : std::cerr << "warning: ignore invalid location for soft pipeline breakers " << elem << std::endl;
402 : : }
403 : 0 : }
404 : : );
405 : 1 : C.arg_parser().add<const char*>(
406 : : /* group= */ "Wasm",
407 : : /* short= */ nullptr,
408 : : /* long= */ "--soft-pipeline-breaker-layout",
409 : : /* description= */ "specify the layout for soft pipeline breakers (`Row`, `PAX4K`, `PAX64K`, `PAX512K`, "
410 : : "`PAX4M`, or `PAX64M`)",
411 : 0 : /* callback= */ [](const char *layout){
412 [ # # ]: 0 : if (streq(layout, "Row")) {
413 : 0 : options::soft_pipeline_breaker_layout = std::make_unique<RowLayoutFactory>();
414 : 0 : } else {
415 : : PAXLayoutFactory::block_size_t size_type;
416 : : uint64_t block_size;
417 [ # # ]: 0 : if (streq(layout, "PAX4K")) {
418 : 0 : size_type = PAXLayoutFactory::NBytes;
419 : 0 : block_size = 1UL << 12;
420 [ # # ]: 0 : } else if (streq(layout, "PAX64K")) {
421 : 0 : size_type = PAXLayoutFactory::NBytes;
422 : 0 : block_size = 1UL << 16;
423 [ # # ]: 0 : } else if (streq(layout, "PAX512K")) {
424 : 0 : size_type = PAXLayoutFactory::NBytes;
425 : 0 : block_size = 1UL << 19;
426 [ # # ]: 0 : } else if (streq(layout, "PAX4M")) {
427 : 0 : size_type = PAXLayoutFactory::NBytes;
428 : 0 : block_size = 1UL << 22;
429 [ # # ]: 0 : } else if (streq(layout, "PAX64M")) {
430 : 0 : size_type = PAXLayoutFactory::NBytes;
431 : 0 : block_size = 1UL << 26;
432 [ # # ]: 0 : } else if (streq(layout, "PAX16Tup")) {
433 : 0 : size_type = PAXLayoutFactory::NTuples;
434 : 0 : block_size = 16;
435 [ # # ]: 0 : } else if (streq(layout, "PAX128Tup")) {
436 : 0 : size_type = PAXLayoutFactory::NTuples;
437 : 0 : block_size = 128;
438 [ # # ]: 0 : } else if (streq(layout, "PAX1024Tup")) {
439 : 0 : size_type = PAXLayoutFactory::NTuples;
440 : 0 : block_size = 1024;
441 : 0 : } else {
442 : 0 : std::cerr << "warning: ignore invalid layout for soft pipeline breakers " << layout << std::endl;
443 : : }
444 : 0 : options::soft_pipeline_breaker_layout = std::make_unique<PAXLayoutFactory>(size_type, block_size);
445 : : }
446 : 0 : }
447 : : );
448 : 1 : C.arg_parser().add<std::size_t>(
449 : : /* group= */ "Wasm",
450 : : /* short= */ nullptr,
451 : : /* long= */ "--soft-pipeline-breaker-num-tuples",
452 : : /* description= */ "set the size in tuples for soft pipeline breakers (0 means infinite)",
453 : 0 : /* callback= */ [](std::size_t num_tuples){ options::soft_pipeline_breaker_num_tuples = num_tuples; }
454 : : );
455 : 1 : C.arg_parser().add<std::size_t>(
456 : : /* group= */ "Wasm",
457 : : /* short= */ nullptr,
458 : : /* long= */ "--index-sequential-scan-batch-size",
459 : : /* description= */ "set the number of tuples ids communicated between host and V8 per batch during index "
460 : : "sequential scan"
461 : : "(0 means infinite), ignored in case of --isam-compile-qualifying",
462 : 0 : /* callback= */ [](std::size_t size){ options::index_sequential_scan_batch_size = size; }
463 : : );
464 : 1 : C.arg_parser().add<std::size_t>(
465 : : /* group= */ "Wasm",
466 : : /* short= */ nullptr,
467 : : /* long= */ "--result-set-window-size",
468 : : /* description= */ "set the window size in tuples for the result set (0 means infinite)",
469 : 0 : /* callback= */ [](std::size_t size){ options::result_set_window_size = size; }
470 : : );
471 : 1 : C.arg_parser().add<bool>(
472 : : /* group= */ "Wasm",
473 : : /* short= */ nullptr,
474 : : /* long= */ "--no-exploit-unique-build",
475 : : /* description= */ "disable potential exploitation of uniqueness of build key in hash joins",
476 : 0 : /* callback= */ [](bool){ options::exploit_unique_build = false; }
477 : : );
478 : 1 : C.arg_parser().add<bool>(
479 : : /* group= */ "Wasm",
480 : : /* short= */ nullptr,
481 : : /* long= */ "--no-simd",
482 : : /* description= */ "disable potential use of SIMDfication",
483 : 0 : /* callback= */ [](bool){ options::simd = false; }
484 : : );
485 : 1 : C.arg_parser().add<bool>(
486 : : /* group= */ "Wasm",
487 : : /* short= */ nullptr,
488 : : /* long= */ "--no-double-pumping",
489 : : /* description= */ "disable use of double pumping (has only an effect if SIMDfication is enabled)",
490 : 0 : /* callback= */ [](bool){ options::double_pumping = false; }
491 : : );
492 : 1 : C.arg_parser().add<std::size_t>(
493 : : /* group= */ "Wasm",
494 : : /* short= */ nullptr,
495 : : /* long= */ "--simd-lanes",
496 : : /* description= */ "set the number of SIMD lanes to prefer",
497 : 0 : /* callback= */ [](std::size_t lanes){ options::simd_lanes = lanes; }
498 : : );
499 : 2 : C.arg_parser().add<std::vector<std::string_view>>(
500 : : /* group= */ "Hacks",
501 : : /* short= */ nullptr,
502 : : /* long= */ "--xxx-asc-sorted-attributes",
503 : : /* description= */ "a comma seperated list of attributes, i.e. of the format `T.x` where `T` is either the "
504 : : "table name or the alias and `x` is the attribute name, which are assumed to be sorted "
505 : : "ascending",
506 : 1 : /* callback= */ [&C](std::vector<std::string_view> attrs){
507 [ # # ]: 0 : for (const auto &elem : attrs) {
508 : 0 : auto idx = elem.find('.');
509 [ # # ]: 0 : if (idx == std::string_view::npos)
510 : 0 : std::cerr << "warning: ignore invalid attribute " << elem << std::endl;
511 [ # # # # : 0 : Schema::Identifier attr(C.pool(elem.substr(0, idx)), C.pool(elem.substr(idx + 1)));
# # # # ]
512 [ # # ]: 0 : options::sorted_attributes.emplace_back(std::move(attr), true);
513 : 0 : }
514 : 0 : }
515 : : );
516 : 2 : C.arg_parser().add<std::vector<std::string_view>>(
517 : : /* group= */ "Hacks",
518 : : /* short= */ nullptr,
519 : : /* long= */ "--xxx-desc-sorted-attributes",
520 : : /* description= */ "a comma seperated list of attributes, i.e. of the format `T.x` where `T` is either the "
521 : : "table name or the alias and `x` is the attribute name, which are assumed to be sorted "
522 : : "descending",
523 : 1 : /* callback= */ [&C](std::vector<std::string_view> attrs){
524 [ # # ]: 0 : for (const auto &elem : attrs) {
525 : 0 : auto idx = elem.find('.');
526 [ # # ]: 0 : if (idx == std::string_view::npos)
527 : 0 : std::cerr << "warning: ignore invalid attribute " << elem << std::endl;
528 [ # # # # : 0 : Schema::Identifier attr(C.pool(elem.substr(0, idx)), C.pool(elem.substr(idx + 1)));
# # # # ]
529 [ # # ]: 0 : options::sorted_attributes.emplace_back(std::move(attr), false);
530 : 0 : }
531 : 0 : }
532 : : );
533 : 1 : }
534 : :
535 : : }
536 : :
537 : :
538 : : /*======================================================================================================================
539 : : * register_wasm_operators()
540 : : *====================================================================================================================*/
541 : :
542 : 0 : void m::register_wasm_operators(PhysicalOptimizer &phys_opt)
543 : : {
544 : 0 : phys_opt.register_operator<NoOp>();
545 : 0 : phys_opt.register_operator<Callback<false>>();
546 : 0 : phys_opt.register_operator<Callback<true>>();
547 : 0 : phys_opt.register_operator<Print<false>>();
548 : 0 : phys_opt.register_operator<Print<true>>();
549 [ # # ]: 0 : if (bool(options::scan_implementations bitand option_configs::ScanImplementation::SCAN)) {
550 : 0 : phys_opt.register_operator<Scan<false>>();
551 [ # # ]: 0 : if (options::simd)
552 : 0 : phys_opt.register_operator<Scan<true>>();
553 : 0 : }
554 [ # # ]: 0 : if (bool(options::scan_implementations bitand option_configs::ScanImplementation::INDEX_SCAN)) {
555 [ # # ]: 0 : if (bool(options::index_implementations bitand option_configs::IndexImplementation::ARRAY))
556 : 0 : phys_opt.register_operator<IndexScan<idx::IndexMethod::Array>>();
557 [ # # ]: 0 : if (bool(options::index_implementations bitand option_configs::IndexImplementation::RMI))
558 : 0 : phys_opt.register_operator<IndexScan<idx::IndexMethod::Rmi>>();
559 : 0 : }
560 [ # # ]: 0 : if (bool(options::filter_selection_strategy bitand option_configs::SelectionStrategy::BRANCHING))
561 : 0 : phys_opt.register_operator<Filter<false>>();
562 [ # # ]: 0 : if (bool(options::filter_selection_strategy bitand option_configs::SelectionStrategy::PREDICATED))
563 : 0 : phys_opt.register_operator<Filter<true>>();
564 : 0 : phys_opt.register_operator<LazyDisjunctiveFilter>();
565 : 0 : phys_opt.register_operator<Projection>();
566 [ # # ]: 0 : if (bool(options::grouping_implementations bitand option_configs::GroupingImplementation::HASH_BASED))
567 : 0 : phys_opt.register_operator<HashBasedGrouping>();
568 [ # # ]: 0 : if (bool(options::grouping_implementations bitand option_configs::GroupingImplementation::ORDERED))
569 : 0 : phys_opt.register_operator<OrderedGrouping>();
570 : 0 : phys_opt.register_operator<Aggregation>();
571 [ # # ]: 0 : if (bool(options::sorting_implementations bitand option_configs::SortingImplementation::QUICKSORT)) {
572 [ # # ]: 0 : if (bool(options::quicksort_cmp_selection_strategy bitand option_configs::SelectionStrategy::BRANCHING))
573 : 0 : phys_opt.register_operator<Quicksort<false>>();
574 [ # # ]: 0 : if (bool(options::quicksort_cmp_selection_strategy bitand option_configs::SelectionStrategy::PREDICATED))
575 : 0 : phys_opt.register_operator<Quicksort<true>>();
576 : 0 : }
577 [ # # ]: 0 : if (bool(options::sorting_implementations bitand option_configs::SortingImplementation::NOOP))
578 : 0 : phys_opt.register_operator<NoOpSorting>();
579 [ # # ]: 0 : if (bool(options::join_implementations bitand option_configs::JoinImplementation::NESTED_LOOPS)) {
580 [ # # ]: 0 : if (bool(options::nested_loops_join_selection_strategy bitand option_configs::SelectionStrategy::BRANCHING))
581 : 0 : phys_opt.register_operator<NestedLoopsJoin<false>>();
582 [ # # ]: 0 : if (bool(options::nested_loops_join_selection_strategy bitand option_configs::SelectionStrategy::PREDICATED))
583 : 0 : phys_opt.register_operator<NestedLoopsJoin<true>>();
584 : 0 : }
585 [ # # ]: 0 : if (bool(options::join_implementations bitand option_configs::JoinImplementation::SIMPLE_HASH)) {
586 [ # # ]: 0 : if (bool(options::simple_hash_join_selection_strategy bitand option_configs::SelectionStrategy::BRANCHING)) {
587 [ + - ]: 1 : phys_opt.register_operator<SimpleHashJoin<false, false>>();
588 [ # # ]: 0 : if (options::exploit_unique_build)
589 [ + - ]: 1 : phys_opt.register_operator<SimpleHashJoin<true, false>>();
590 [ + - ]: 1 : }
591 [ # # ]: 0 : if (bool(options::simple_hash_join_selection_strategy bitand option_configs::SelectionStrategy::PREDICATED)) {
592 : 0 : phys_opt.register_operator<SimpleHashJoin<false, true>>();
593 [ # # ]: 0 : if (options::exploit_unique_build)
594 : 0 : phys_opt.register_operator<SimpleHashJoin<true, true>>();
595 : 0 : }
596 : 0 : }
597 [ # # ]: 0 : if (bool(options::join_implementations bitand option_configs::JoinImplementation::SORT_MERGE)) {
598 [ # # ]: 0 : if (bool(options::sort_merge_join_selection_strategy bitand option_configs::SelectionStrategy::BRANCHING)) {
599 [ # # ]: 0 : if (bool(options::sort_merge_join_cmp_selection_strategy bitand option_configs::SelectionStrategy::BRANCHING)) {
600 : 0 : phys_opt.register_operator<SortMergeJoin<false, false, false, false>>();
601 : 0 : phys_opt.register_operator<SortMergeJoin<false, true, false, false>>();
602 : 0 : phys_opt.register_operator<SortMergeJoin<true, false, false, false>>();
603 : 0 : phys_opt.register_operator<SortMergeJoin<true, true, false, false>>();
604 : 0 : }
605 [ # # ]: 0 : if (bool(options::sort_merge_join_cmp_selection_strategy bitand option_configs::SelectionStrategy::PREDICATED)) {
606 : 0 : phys_opt.register_operator<SortMergeJoin<false, false, false, true>>();
607 : 0 : phys_opt.register_operator<SortMergeJoin<false, true, false, true>>();
608 : 0 : phys_opt.register_operator<SortMergeJoin<true, false, false, true>>();
609 : 0 : phys_opt.register_operator<SortMergeJoin<true, true, false, true>>();
610 : 0 : }
611 : 0 : }
612 [ # # ]: 0 : if (bool(options::sort_merge_join_selection_strategy bitand option_configs::SelectionStrategy::PREDICATED)) {
613 [ # # ]: 0 : if (bool(options::sort_merge_join_cmp_selection_strategy bitand option_configs::SelectionStrategy::BRANCHING)) {
614 : 0 : phys_opt.register_operator<SortMergeJoin<false, false, true, false>>();
615 : 0 : phys_opt.register_operator<SortMergeJoin<false, true, true, false>>();
616 : 0 : phys_opt.register_operator<SortMergeJoin<true, false, true, false>>();
617 : 0 : phys_opt.register_operator<SortMergeJoin<true, true, true, false>>();
618 : 0 : }
619 [ # # ]: 0 : if (bool(options::sort_merge_join_cmp_selection_strategy bitand option_configs::SelectionStrategy::PREDICATED)) {
620 : 0 : phys_opt.register_operator<SortMergeJoin<false, false, true, true>>();
621 : 0 : phys_opt.register_operator<SortMergeJoin<false, true, true, true>>();
622 : 0 : phys_opt.register_operator<SortMergeJoin<true, false, true, true>>();
623 : 0 : phys_opt.register_operator<SortMergeJoin<true, true, true, true>>();
624 : 0 : }
625 : 0 : }
626 : 0 : }
627 : 0 : phys_opt.register_operator<Limit>();
628 [ # # ]: 0 : if (options::hash_based_group_join)
629 : 0 : phys_opt.register_operator<HashBasedGroupJoin>();
630 : 0 : }
631 : :
632 : :
633 : : /*======================================================================================================================
634 : : * Helper structs and functions
635 : : *====================================================================================================================*/
636 : :
637 : : /** Emits code to write the result set of the `Schema` \p schema using the `DataLayout` created by \p factory. The
638 : : * result set is either materialized entirely (if \p window_size equals 0 indicating infinity) or only partially (if
639 : : * \p window_size does not equal 0 indicating the used batch size). To emit the code at the correct position, code
640 : : * generation is delegated to the child physical operator \p child. */
641 : 0 : void write_result_set(const Schema &schema, const DataLayoutFactory &factory, uint32_t window_size,
642 : : const m::wasm::MatchBase &child)
643 : : {
644 [ # # # # : 0 : M_insist(schema == schema.drop_constants().deduplicate(), "schema must not contain constants or duplicates");
# # ]
645 : 0 : M_insist(CodeGenContext::Get().env().empty(), "all environment entries must be used");
646 : :
647 : : /*----- Set data layout factory used for the result set. -----*/
648 : 0 : auto &context = WasmEngine::Get_Wasm_Context_By_ID(Module::ID());
649 : 0 : context.result_set_factory = factory.clone();
650 : :
651 [ # # ]: 0 : if (schema.num_entries() == 0) { // result set contains only NULL constants
652 [ # # ]: 0 : if (window_size != 0) { // i.e. result set is materialized only partially
653 : 0 : M_insist(window_size >= CodeGenContext::Get().num_simd_lanes());
654 : 0 : M_insist(window_size % CodeGenContext::Get().num_simd_lanes() == 0);
655 : :
656 : 0 : std::optional<Var<U32x1>> counter; ///< variable to *locally* count
657 : : ///> *global* counter backup since the following code may be called multiple times
658 [ # # ]: 0 : Global<U32x1> counter_backup; // default initialized to 0
659 : :
660 : : /*----- Create child function s.t. result set is extracted in case of returns (e.g. due to `Limit`). -----*/
661 [ # # # # : 0 : FUNCTION(child_pipeline, void(void))
# # # # ]
662 : : {
663 [ # # # # ]: 0 : auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
664 : :
665 [ # # ]: 0 : child.execute(
666 [ # # ]: 0 : /* setup= */ setup_t::Make_Without_Parent([&](){ counter.emplace(counter_backup); }),
667 : 0 : /* pipeline= */ [&](){
668 : 0 : M_insist(bool(counter));
669 : :
670 : : /*----- Increment tuple ID. -----*/
671 [ # # ]: 0 : if (auto &env = CodeGenContext::Get().env(); env.predicated()) {
672 : 0 : M_insist(CodeGenContext::Get().num_simd_lanes() == 1,
673 : : "SIMDfication with predication not supported");
674 [ # # # # : 0 : *counter += env.extract_predicate<_Boolx1>().is_true_and_not_null().to<uint32_t>();
# # ]
675 : 0 : } else {
676 : 0 : *counter += uint32_t(CodeGenContext::Get().num_simd_lanes());
677 : : }
678 : :
679 : : /*----- If window size is reached, update result size, extract current results, and reset tuple ID. */
680 [ # # ]: 0 : IF (*counter == window_size) {
681 [ # # ]: 0 : CodeGenContext::Get().inc_num_tuples(U32x1(window_size));
682 [ # # # # ]: 0 : Module::Get().emit_call<void>("read_result_set", Ptr<void>::Nullptr(), U32x1(window_size));
683 : 0 : *counter = 0U;
684 : 0 : };
685 : 0 : },
686 [ # # ]: 0 : /* teardown= */ teardown_t::Make_Without_Parent([&](){
687 : 0 : M_insist(bool(counter));
688 : 0 : counter_backup = *counter;
689 : 0 : counter.reset();
690 : 0 : })
691 : : );
692 : 0 : }
693 [ # # ]: 0 : child_pipeline(); // call child function
694 : :
695 : : /*----- Update number of result tuples. -----*/
696 [ # # # # : 0 : CodeGenContext::Get().inc_num_tuples(counter_backup);
# # ]
697 : :
698 : : /*----- Extract remaining results. -----*/
699 [ # # # # : 0 : Module::Get().emit_call<void>("read_result_set", Ptr<void>::Nullptr(), counter_backup.val());
# # # # ]
700 : 0 : } else { // i.e. result set is materialized entirely
701 : : /*----- Create child function s.t. result set is extracted in case of returns (e.g. due to `Limit`). -----*/
702 [ # # # # : 0 : FUNCTION(child_pipeline, void(void))
# # ]
703 : : {
704 [ # # # # ]: 0 : auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
705 : :
706 : 0 : std::optional<Var<U32x1>> num_tuples; ///< variable to *locally* count additional result tuples
707 : :
708 [ # # ]: 0 : child.execute(
709 [ # # ]: 0 : /* setup= */ setup_t::Make_Without_Parent([&](){
710 [ # # ]: 0 : num_tuples.emplace(CodeGenContext::Get().num_tuples());
711 : 0 : }),
712 : 0 : /* pipeline= */ [&](){
713 : 0 : M_insist(bool(num_tuples));
714 [ # # ]: 0 : if (auto &env = CodeGenContext::Get().env(); env.predicated()) {
715 : 0 : M_insist(CodeGenContext::Get().num_simd_lanes() == 1,
716 : : "SIMDfication with predication not supported");
717 [ # # # # : 0 : *num_tuples += env.extract_predicate<_Boolx1>().is_true_and_not_null().to<uint32_t>();
# # ]
718 : 0 : } else {
719 : 0 : *num_tuples += uint32_t(CodeGenContext::Get().num_simd_lanes());
720 : : }
721 : 0 : },
722 [ # # ]: 0 : /* teardown= */ teardown_t::Make_Without_Parent([&](){
723 : 0 : M_insist(bool(num_tuples));
724 [ # # ]: 0 : CodeGenContext::Get().set_num_tuples(*num_tuples);
725 : 0 : num_tuples.reset();
726 : 0 : })
727 : : );
728 : 0 : }
729 [ # # ]: 0 : child_pipeline(); // call child function
730 : :
731 : : /*----- Extract all results at once. -----*/
732 [ # # # # : 0 : Module::Get().emit_call<void>("read_result_set", Ptr<void>::Nullptr(), CodeGenContext::Get().num_tuples());
# # # # #
# ]
733 : 0 : }
734 : 0 : } else { // result set contains contains actual values
735 [ # # ]: 0 : if (window_size != 0) { // i.e. result set is materialized only partially
736 : 0 : M_insist(window_size > CodeGenContext::Get().num_simd_lanes());
737 : 0 : M_insist(window_size % CodeGenContext::Get().num_simd_lanes() == 0);
738 : :
739 : : /*----- Create finite global buffer (without `pipeline`-callback) used as reusable result set. -----*/
740 [ # # # # : 0 : GlobalBuffer result_set(schema, factory, false, window_size); // no callback to extract windows manually
# # ]
741 : :
742 : : /*----- Create child function s.t. result set is extracted in case of returns (e.g. due to `Limit`). -----*/
743 [ # # # # : 0 : FUNCTION(child_pipeline, void(void))
# # # # ]
744 : : {
745 [ # # # # ]: 0 : auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
746 : :
747 [ # # ]: 0 : child.execute(
748 [ # # ]: 0 : /* setup= */ setup_t::Make_Without_Parent([&](){ result_set.setup(); }),
749 : 0 : /* pipeline= */ [&](){
750 : : /*----- Store whether only a single slot is free to not extract result for empty buffer. -----*/
751 [ # # ]: 0 : const Var<Boolx1> single_slot_free(
752 [ # # # # ]: 0 : result_set.size() == window_size - uint32_t(CodeGenContext::Get().num_simd_lanes())
753 : : );
754 : :
755 : : /*----- Write the result. -----*/
756 [ # # ]: 0 : result_set.consume(); // also resets size to 0 in case buffer has reached window size
757 : :
758 : : /*----- If the last buffer slot was filled, update result size and extract current results. */
759 [ # # # # : 0 : IF (single_slot_free and result_set.size() == 0U) {
# # # # ]
760 [ # # ]: 0 : CodeGenContext::Get().inc_num_tuples(U32x1(window_size));
761 [ # # ]: 0 : Module::Get().emit_call<void>("read_result_set", result_set.base_address(),
762 [ # # ]: 0 : U32x1(window_size));
763 : 0 : };
764 : 0 : },
765 [ # # ]: 0 : /* teardown= */ teardown_t::Make_Without_Parent([&](){ result_set.teardown(); })
766 : : );
767 : 0 : }
768 [ # # ]: 0 : child_pipeline(); // call child function
769 : :
770 : : /*----- Update number of result tuples. -----*/
771 [ # # # # : 0 : CodeGenContext::Get().inc_num_tuples(result_set.size());
# # ]
772 : :
773 : : /*----- Extract remaining results. -----*/
774 [ # # # # : 0 : Module::Get().emit_call<void>("read_result_set", result_set.base_address(), result_set.size());
# # # # ]
775 : 0 : } else { // i.e. result set is materialized entirely
776 : : /*----- Create infinite global buffer (without `pipeline`-callback) used as single result set. -----*/
777 [ # # # # : 0 : GlobalBuffer result_set(schema, factory); // no callback to extract results all at once
# # ]
778 : :
779 : : /*----- Create child function s.t. result set is extracted in case of returns (e.g. due to `Limit`). -----*/
780 [ # # # # : 0 : FUNCTION(child_pipeline, void(void))
# # # # ]
781 : : {
782 [ # # # # ]: 0 : auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
783 : :
784 [ # # ]: 0 : child.execute(
785 [ # # ]: 0 : /* setup= */ setup_t::Make_Without_Parent([&](){ result_set.setup(); }),
786 : 0 : /* pipeline= */ [&](){ result_set.consume(); },
787 [ # # ]: 0 : /* teardown= */ teardown_t::Make_Without_Parent([&](){ result_set.teardown(); })
788 : : );
789 : 0 : }
790 [ # # ]: 0 : child_pipeline(); // call child function
791 : :
792 : : /*----- Set number of result tuples. -----*/
793 [ # # # # : 0 : CodeGenContext::Get().inc_num_tuples(result_set.size()); // not inside child function due to predication
# # ]
794 : :
795 : : /*----- Extract all results at once. -----*/
796 [ # # # # : 0 : Module::Get().emit_call<void>("read_result_set", result_set.base_address(), result_set.size());
# # # # ]
797 : 0 : }
798 : : }
799 : 0 : }
800 : :
801 : : ///> helper struct for aggregates
802 : : struct aggregate_info_t
803 : : {
804 : : Schema::entry_type entry; ///< aggregate entry consisting of identifier, type, and constraints
805 : : m::Function::fnid_t fnid; ///< aggregate function
806 : : const std::vector<std::unique_ptr<ast::Expr>> &args; ///< aggregate arguments
807 : : };
808 : :
809 : : ///> helper struct for AVG aggregates
810 : 0 : struct avg_aggregate_info_t
811 : : {
812 : : Schema::Identifier running_count; ///< identifier of running count
813 : : Schema::Identifier sum; ///< potential identifier for sum (only set if AVG is computed once at the end)
814 : : bool compute_running_avg; ///< flag whether running AVG must be computed instead of one computation at the end
815 : : };
816 : :
817 : : /** Computes and returns information about the aggregates \p aggregates which are contained in the schema \p schema
818 : : * starting at offset \p aggregates_offset. The firstly returned element contains general information about each
819 : : * aggregates like its identifier, type, function type, and arguments. The secondly returned element contains
820 : : * additional information about each AVG aggregates like a flag to determine whether it can be computed using a
821 : : * running AVG or lazily at the end. Either way, the corresponding running count and an optional sum are contained
822 : : * in these elements, too. */
823 : : std::pair<std::vector<aggregate_info_t>, std::unordered_map<Schema::Identifier, avg_aggregate_info_t>>
824 : 0 : compute_aggregate_info(const std::vector<std::reference_wrapper<const FnApplicationExpr>> &aggregates,
825 : : const Schema &schema, std::size_t aggregates_offset = 0)
826 : : {
827 : 0 : std::vector<aggregate_info_t> aggregates_info;
828 : 0 : std::unordered_map<Schema::Identifier, avg_aggregate_info_t> avg_aggregates_info;
829 : :
830 [ # # # # ]: 0 : for (std::size_t i = aggregates_offset; i < schema.num_entries(); ++i) {
831 [ # # ]: 0 : auto &e = schema[i];
832 : :
833 : 0 : auto pred = [&e](const auto &info){ return info.entry.id == e.id; };
834 [ # # # # ]: 0 : if (auto it = std::find_if(aggregates_info.cbegin(), aggregates_info.cend(), pred); it != aggregates_info.cend())
835 : 0 : continue; // duplicated aggregate
836 : :
837 : 0 : auto &fn_expr = aggregates[i - aggregates_offset].get();
838 [ # # ]: 0 : auto &fn = fn_expr.get_function();
839 [ # # ]: 0 : M_insist(fn.kind == m::Function::FN_Aggregate, "not an aggregation function");
840 : :
841 [ # # ]: 0 : if (fn.fnid == m::Function::FN_AVG) {
842 [ # # ]: 0 : M_insist(fn_expr.args.size() == 1, "AVG aggregate function expects exactly one argument");
843 : :
844 : : /*----- Insert a suitable running count, i.e. COUNT over the argument of the AVG aggregate. -----*/
845 : 0 : auto pred = [&fn_expr](const auto &_fn_expr){
846 [ # # ]: 0 : M_insist(_fn_expr.get().get_function().fnid != m::Function::FN_COUNT or _fn_expr.get().args.size() <= 1,
847 : : "COUNT aggregate function expects exactly one argument");
848 [ # # ]: 0 : return _fn_expr.get().get_function().fnid == m::Function::FN_COUNT and
849 [ # # ]: 0 : not _fn_expr.get().args.empty() and *_fn_expr.get().args[0] == *fn_expr.args[0];
850 : : };
851 : 0 : std::optional<Schema::Identifier> running_count;
852 [ # # # # : 0 : if (auto it = std::find_if(aggregates.cbegin(), aggregates.cend(), pred);
# # ]
853 : 0 : it != aggregates.cend())
854 : : { // reuse found running count
855 [ # # ]: 0 : const auto idx_agg = std::distance(aggregates.cbegin(), it);
856 [ # # # # ]: 0 : running_count = schema[aggregates_offset + idx_agg].id;
857 : 0 : } else { // insert additional running count
858 [ # # ]: 0 : std::ostringstream oss;
859 [ # # # # ]: 0 : oss << "$running_count_" << fn_expr;
860 [ # # # # : 0 : running_count = Schema::Identifier(Catalog::Get().pool(oss.str().c_str()));
# # # # #
# ]
861 [ # # # # ]: 0 : aggregates_info.emplace_back(aggregate_info_t{
862 [ # # # # : 0 : .entry = { *running_count, Type::Get_Integer(Type::TY_Scalar, 8), Schema::entry_type::NOT_NULLABLE },
# # # # ]
863 : : .fnid = m::Function::FN_COUNT,
864 : 0 : .args = fn_expr.args
865 : : });
866 : 0 : }
867 : :
868 : : /*----- Decide how to compute the average aggregate and insert sum aggregate accordingly. -----*/
869 : 0 : std::optional<Schema::Identifier> sum;
870 : : bool compute_running_avg;
871 [ # # # # : 0 : if (fn_expr.args[0]->type()->size() <= 32) {
# # ]
872 : : /* Compute average by summing up all values in a 64-bit field (thus no overflows should occur) and
873 : : * dividing by the running count once at the end. */
874 : 0 : compute_running_avg = false;
875 : 0 : auto pred = [&fn_expr](const auto &_fn_expr){
876 [ # # ]: 0 : M_insist(_fn_expr.get().get_function().fnid != m::Function::FN_SUM or
877 : : _fn_expr.get().args.size() == 1,
878 : : "SUM aggregate function expects exactly one argument");
879 [ # # ]: 0 : return _fn_expr.get().get_function().fnid == m::Function::FN_SUM and
880 : 0 : *_fn_expr.get().args[0] == *fn_expr.args[0];
881 : : };
882 [ # # # # : 0 : if (auto it = std::find_if(aggregates.cbegin(), aggregates.cend(), pred);
# # ]
883 : 0 : it != aggregates.cend())
884 : : { // reuse found SUM aggregate
885 [ # # ]: 0 : const auto idx_agg = std::distance(aggregates.cbegin(), it);
886 [ # # # # ]: 0 : sum = schema[aggregates_offset + idx_agg].id;
887 : 0 : } else { // insert additional SUM aggregate
888 [ # # ]: 0 : std::ostringstream oss;
889 [ # # # # ]: 0 : oss << "$sum_" << fn_expr;
890 [ # # # # : 0 : sum = Schema::Identifier(Catalog::Get().pool(oss.str().c_str()));
# # # # #
# ]
891 : : const Type *type;
892 [ # # # # : 0 : switch (as<const Numeric>(*fn_expr.args[0]->type()).kind) {
# # ]
893 : : case Numeric::N_Int:
894 : : case Numeric::N_Decimal:
895 [ # # # # ]: 0 : type = Type::Get_Integer(Type::TY_Scalar, 8);
896 : 0 : break;
897 : : case Numeric::N_Float:
898 [ # # # # ]: 0 : type = Type::Get_Double(Type::TY_Scalar);
899 : 0 : }
900 [ # # # # ]: 0 : aggregates_info.emplace_back(aggregate_info_t{
901 [ # # # # ]: 0 : .entry = { *sum, type, e.constraints },
902 : : .fnid = m::Function::FN_SUM,
903 : 0 : .args = fn_expr.args
904 : : });
905 : 0 : }
906 : 0 : } else {
907 : : /* Compute average by computing a running average for each inserted value in a `_Doublex1` field (since
908 : : * the sum may overflow). */
909 : 0 : compute_running_avg = true;
910 [ # # # # ]: 0 : M_insist(e.type->is_double());
911 [ # # # # ]: 0 : aggregates_info.emplace_back(aggregate_info_t{
912 [ # # ]: 0 : .entry = e,
913 : : .fnid = m::Function::FN_AVG,
914 : 0 : .args = fn_expr.args
915 : : });
916 : : }
917 : :
918 : : /*----- Add info for this AVG aggregate. -----*/
919 [ # # # # ]: 0 : avg_aggregates_info.try_emplace(e.id, avg_aggregate_info_t{
920 [ # # ]: 0 : .running_count = std::move(*running_count),
921 [ # # ]: 0 : .sum = std::move(*sum),
922 : 0 : .compute_running_avg = compute_running_avg
923 : : });
924 : 0 : } else {
925 [ # # # # : 0 : aggregates_info.emplace_back(aggregate_info_t{
# # ]
926 [ # # ]: 0 : .entry = e,
927 : 0 : .fnid = fn.fnid,
928 : 0 : .args = fn_expr.args
929 : : });
930 : : }
931 : 0 : }
932 : :
933 : 0 : return { std::move(aggregates_info), std::move(avg_aggregates_info) };
934 : 0 : }
935 : :
936 : : /** Decompose the equi-predicate \p cnf, i.e. a conjunction of equality comparisons of each two designators, into all
937 : : * identifiers contained in schema \p schema_left (returned as first element) and all identifiers not contained in
938 : : * the aforementioned schema (return as second element). */
939 : : std::pair<std::vector<Schema::Identifier>, std::vector<Schema::Identifier>>
940 : 0 : decompose_equi_predicate(const cnf::CNF &cnf, const Schema &schema_left)
941 : : {
942 : 0 : std::vector<Schema::Identifier> ids_left, ids_right;
943 [ # # ]: 0 : for (auto &clause : cnf) {
944 [ # # ]: 0 : M_insist(clause.size() == 1, "invalid equi-predicate");
945 : 0 : auto &literal = clause[0];
946 [ # # # # ]: 0 : auto &binary = as<const BinaryExpr>(literal.expr());
947 [ # # # # : 0 : M_insist((not literal.negative() and binary.tok == TK_EQUAL) or
# # # # #
# # # #
# ]
948 : : (literal.negative() and binary.tok == TK_BANG_EQUAL), "invalid equi-predicate");
949 [ # # # # ]: 0 : M_insist(is<const Designator>(binary.lhs), "invalid equi-predicate");
950 [ # # # # ]: 0 : M_insist(is<const Designator>(binary.rhs), "invalid equi-predicate");
951 [ # # # # ]: 0 : Schema::Identifier id_first(*binary.lhs), id_second(*binary.rhs);
952 [ # # # # : 0 : const auto &[id_left, id_right] = schema_left.has(id_first) ? std::make_pair(id_first, id_second)
# # ]
953 [ # # ]: 0 : : std::make_pair(id_second, id_first);
954 [ # # ]: 0 : ids_left.push_back(std::move(id_left));
955 [ # # ]: 0 : ids_right.push_back(std::move(id_right));
956 : 0 : }
957 [ # # ]: 0 : M_insist(ids_left.size() == ids_right.size(), "number of found IDs differ");
958 [ # # ]: 0 : M_insist(not ids_left.empty(), "must find at least one ID");
959 : 0 : return { std::move(ids_left), std::move(ids_right) };
960 : 0 : }
961 : :
962 : : /** Returns the number of rows of table \p table_name. */
963 : 10 : U32x1 get_num_rows(const ThreadSafePooledString &table_name) {
964 [ + + - + : 10 : static std::ostringstream oss;
+ - ]
965 [ + - + - ]: 10 : oss.str("");
966 : 10 : oss << table_name << "_num_rows";
967 [ + - ]: 10 : return Module::Get().get_global<uint32_t>(oss.str().c_str());
968 : 0 : }
969 : :
970 : : /** Returns a pointer to the beginning of table \p table_name in the WebAssembly linear memory. */
971 : 10 : Ptr<void> get_base_address(const ThreadSafePooledString &table_name) {
972 [ + + - + : 10 : static std::ostringstream oss;
+ - ]
973 [ + - + - ]: 10 : oss.str("");
974 : 10 : oss << table_name << "_mem";
975 [ + - ]: 10 : return Module::Get().get_global<void*>(oss.str().c_str());
976 : 0 : }
977 : :
978 : : /** Computes the initial hash table capacity for \p op. The function ensures that the initial capacity is in the range
979 : : * [0, 2^32 - 1] such that the capacity does *not* exceed the `uint32_t` value limit. */
980 : 0 : uint32_t compute_initial_ht_capacity(const Operator &op, double load_factor) {
981 : : uint64_t initial_capacity;
982 [ # # ]: 0 : if (options::hash_table_initial_capacity) {
983 : 0 : initial_capacity = *options::hash_table_initial_capacity;
984 : 0 : } else {
985 [ # # ]: 0 : if (op.has_info())
986 : 0 : initial_capacity = static_cast<uint64_t>(std::ceil(op.info().estimated_cardinality / load_factor));
987 [ # # ]: 0 : else if (auto scan = cast<const ScanOperator>(&op))
988 : 0 : initial_capacity = static_cast<uint64_t>(std::ceil(scan->store().num_rows() / load_factor));
989 : : else
990 : 0 : initial_capacity = 1024; // fallback
991 : : }
992 [ # # ]: 0 : return std::in_range<uint32_t>(initial_capacity) ? initial_capacity : std::numeric_limits<uint32_t>::max();
993 : : }
994 : :
995 : : ///> helper struct holding the bounds for index scan
996 : 0 : struct index_scan_bounds_t
997 : : {
998 : 0 : Schema::entry_type attribute = Schema::entry_type::CreateArtificial(); ///< Attribute for which bounds should hold
999 : : std::optional<std::reference_wrapper<const ast::Expr>> lo, hi; ///< lo and hi bounds
1000 : : bool is_inclusive_lo, is_inclusive_hi; ///< flag to indicate if bounds are inclusive
1001 : : };
1002 : :
1003 : : /** Returns `true` iff \p expr is a valid bound. */
1004 : 0 : bool is_valid_bound(const ast::Expr &expr) {
1005 [ # # ]: 0 : if (is<const Constant>(expr))
1006 : 0 : return true;
1007 [ # # ]: 0 : if (auto u = cast<const UnaryExpr>(&expr)) {
1008 : : /* `UnaryExpr` must be followed by `Constant`. */
1009 [ # # # # : 0 : if ((u->op().type == TK_MINUS or u->op().type == TK_PLUS) and is<const Constant>(*u->expr))
# # # # #
# # # #
# ]
1010 : 0 : return true;
1011 : 0 : }
1012 : 0 : return false;
1013 : 0 : }
1014 : :
1015 : : /** Given an `Expr` \p expr representing a valid bound, returns a pair consiting of a constant and a boolean flag
1016 : : * indicating whether the constant is negative. */
1017 : 0 : std::pair<const Constant&, bool> get_valid_bound(const ast::Expr &expr) {
1018 : 0 : M_insist(is_valid_bound(expr), "bound must be valid");
1019 [ # # ]: 0 : if (auto c = cast<const Constant>(&expr))
1020 : 0 : return { *c, false };
1021 : 0 : auto &u = as<const UnaryExpr>(expr);
1022 : 0 : return { as<const Constant>(*u.expr), u.op().type == TK_MINUS };
1023 : 0 : }
1024 : :
1025 : : /** Extracts the bounds for performing index scan from CNF \p cnf. CNFs must either consist of
1026 : : * 1. a single equality predicate, e.g. "x = 42" or
1027 : : * 2. one or two predicates defining a (closed or open) range, e.g. "x > 42 AND x < 109" or "x < 42". */
1028 : 0 : index_scan_bounds_t extract_index_scan_bounds(const cnf::CNF &cnf)
1029 : : {
1030 : 0 : index_scan_bounds_t bounds;
1031 : :
1032 [ # # ]: 0 : M_insist(not cnf.empty(), "filter condition must not be empty");
1033 [ # # ]: 0 : auto designators = cnf.get_required();
1034 [ # # # # ]: 0 : M_insist(designators.num_entries() == 1, "filter condition must contain exactly one designator");
1035 [ # # # # ]: 0 : bounds.attribute = designators[0];
1036 : :
1037 [ # # ]: 0 : for (auto &clause : cnf) {
1038 [ # # ]: 0 : M_insist(clause.size() == 1, "invalid predicate");
1039 : 0 : auto &literal = clause[0];
1040 [ # # # # ]: 0 : auto &binary = as<const BinaryExpr>(literal.expr());
1041 [ # # # # : 0 : M_insist((is<const Designator>(binary.lhs) and is_valid_bound(*binary.rhs)) or
# # # # #
# # # #
# ]
1042 : : (is<const Designator>(binary.rhs) and is_valid_bound(*binary.lhs)), "invalid predicate");
1043 [ # # ]: 0 : bool has_attribute_left = is<const Designator>(binary.lhs);
1044 [ # # ]: 0 : auto &bound = has_attribute_left ? *binary.rhs : *binary.lhs;
1045 : :
1046 [ # # # # : 0 : switch(binary.tok.type) {
# # ]
1047 : : default:
1048 [ # # ]: 0 : M_unreachable("unsupported token type");
1049 : : case TK_EQUAL:
1050 [ # # # # ]: 0 : M_insist(not bool(bounds.lo) and not bool(bounds.hi), "bound already set");
1051 : 0 : bounds.lo = bounds.hi = std::cref(bound);
1052 : 0 : bounds.is_inclusive_lo = bounds.is_inclusive_hi = true;
1053 : 0 : break;
1054 : : case TK_GREATER:
1055 [ # # ]: 0 : if (has_attribute_left) {
1056 [ # # ]: 0 : M_insist(not bool(bounds.lo), "lo bound already set");
1057 : 0 : bounds.lo = std::cref(bound);
1058 : 0 : bounds.is_inclusive_lo = false;
1059 : 0 : } else {
1060 [ # # ]: 0 : M_insist(not bool(bounds.hi), "hi bound already set");
1061 : 0 : bounds.hi = std::cref(bound);
1062 : 0 : bounds.is_inclusive_hi = false;
1063 : : }
1064 : 0 : break;
1065 : : case TK_GREATER_EQUAL:
1066 [ # # ]: 0 : if (has_attribute_left) {
1067 [ # # ]: 0 : M_insist(not bool(bounds.lo), "lo bound already set");
1068 : 0 : bounds.lo = std::cref(bound);
1069 : 0 : bounds.is_inclusive_lo = true;
1070 : 0 : } else {
1071 [ # # ]: 0 : M_insist(not bool(bounds.hi), "hi bound already set");
1072 : 0 : bounds.hi = std::cref(bound);
1073 : 0 : bounds.is_inclusive_hi = true;
1074 : : }
1075 : 0 : break;
1076 : : case TK_LESS:
1077 [ # # ]: 0 : if (has_attribute_left) {
1078 [ # # ]: 0 : M_insist(not bool(bounds.hi), "hi bound already set");
1079 : 0 : bounds.hi = std::cref(bound);
1080 : 0 : bounds.is_inclusive_hi = false;
1081 : 0 : } else {
1082 [ # # ]: 0 : M_insist(not bool(bounds.lo), "lo bound already set");
1083 : 0 : bounds.lo = std::cref(bound);
1084 : 0 : bounds.is_inclusive_lo = false;
1085 : : }
1086 : 0 : break;
1087 : : case TK_LESS_EQUAL:
1088 [ # # ]: 0 : if (has_attribute_left) {
1089 [ # # ]: 0 : M_insist(not bool(bounds.hi), "hi bound already set");
1090 : 0 : bounds.hi = std::cref(bound);
1091 : 0 : bounds.is_inclusive_hi = true;
1092 : 0 : } else {
1093 [ # # ]: 0 : M_insist(not bool(bounds.lo), "lo bound already set");
1094 : 0 : bounds.lo = std::cref(bound);
1095 : 0 : bounds.is_inclusive_lo = true;
1096 : : }
1097 : 0 : break;
1098 : : }
1099 : : }
1100 [ # # # # ]: 0 : M_insist(bool(bounds.lo) or bool(bounds.hi), "either bound must be set");
1101 : 0 : return bounds;
1102 [ # # ]: 0 : }
1103 : :
1104 : :
1105 : : /*======================================================================================================================
1106 : : * NoOp
1107 : : *====================================================================================================================*/
1108 : :
1109 : 0 : void NoOp::execute(const Match<NoOp> &M, setup_t, pipeline_t, teardown_t)
1110 : : {
1111 : 0 : std::optional<Var<U32x1>> num_tuples; ///< variable to *locally* count additional result tuples
1112 : :
1113 [ # # ]: 0 : M.child->execute(
1114 [ # # # # ]: 0 : /* setup= */ setup_t::Make_Without_Parent([&](){ num_tuples.emplace(CodeGenContext::Get().num_tuples()); }),
1115 : 0 : /* pipeline= */ [&](){
1116 : 0 : M_insist(bool(num_tuples));
1117 [ # # ]: 0 : if (auto &env = CodeGenContext::Get().env(); env.predicated()) {
1118 [ # # # ]: 0 : switch (CodeGenContext::Get().num_simd_lanes()) {
1119 : 0 : default: M_unreachable("invalid number of simd lanes");
1120 : : case 1: {
1121 [ # # # # : 0 : *num_tuples += env.extract_predicate<_Boolx1>().is_true_and_not_null().to<uint32_t>();
# # ]
1122 : 0 : break;
1123 : : }
1124 : : case 16: {
1125 [ # # ]: 0 : auto pred = env.extract_predicate<_Boolx16>().is_true_and_not_null();
1126 [ # # # # : 0 : *num_tuples += pred.bitmask().popcnt();
# # ]
1127 : : break;
1128 : 0 : }
1129 : : }
1130 : 0 : } else {
1131 : 0 : *num_tuples += uint32_t(CodeGenContext::Get().num_simd_lanes());
1132 : : }
1133 : 0 : },
1134 [ # # ]: 0 : /* teardown= */ teardown_t::Make_Without_Parent([&](){
1135 : 0 : M_insist(bool(num_tuples));
1136 [ # # ]: 0 : CodeGenContext::Get().set_num_tuples(*num_tuples);
1137 : 0 : num_tuples.reset();
1138 : 0 : })
1139 : : );
1140 : 0 : }
1141 : :
1142 : :
1143 : : /*======================================================================================================================
1144 : : * Callback
1145 : : *====================================================================================================================*/
1146 : :
1147 : : template<bool SIMDfied>
1148 : 0 : ConditionSet Callback<SIMDfied>::pre_condition(std::size_t child_idx, const std::tuple<const CallbackOperator*>&)
1149 : : {
1150 : 0 : M_insist(child_idx == 0);
1151 : :
1152 : 0 : ConditionSet pre_cond;
1153 : :
1154 : : if constexpr (SIMDfied) {
1155 : : /*----- SIMDfied callback supports SIMD but not predication. -----*/
1156 [ # # # # ]: 0 : pre_cond.add_condition(Predicated(false));
1157 : : } else {
1158 : : /*----- Non-SIMDfied callback does not support SIMD. -----*/
1159 [ # # # # ]: 0 : pre_cond.add_condition(NoSIMD());
1160 : : }
1161 : :
1162 : 0 : return pre_cond;
1163 [ # # # # ]: 0 : }
1164 : :
1165 : : template<bool SIMDfied>
1166 : 0 : void Callback<SIMDfied>::execute(const Match<Callback> &M, setup_t, pipeline_t, teardown_t)
1167 : : {
1168 : 0 : M_insist(bool(M.result_set_factory), "`wasm::Callback` must have a factory for the result set");
1169 : :
1170 [ # # # # ]: 0 : auto result_set_schema = M.callback.schema().drop_constants().deduplicate();
1171 [ # # # # ]: 0 : write_result_set(result_set_schema, *M.result_set_factory, M.result_set_window_size, *M.child);
1172 : 0 : }
1173 : :
1174 : :
1175 : : /*======================================================================================================================
1176 : : * Print
1177 : : *====================================================================================================================*/
1178 : :
1179 : : template<bool SIMDfied>
1180 : 0 : ConditionSet Print<SIMDfied>::pre_condition(std::size_t child_idx, const std::tuple<const PrintOperator*>&)
1181 : : {
1182 : 0 : M_insist(child_idx == 0);
1183 : :
1184 : 0 : ConditionSet pre_cond;
1185 : :
1186 : : if constexpr (SIMDfied) {
1187 : : /*----- SIMDfied print supports SIMD but not predication. -----*/
1188 [ # # # # ]: 0 : pre_cond.add_condition(Predicated(false));
1189 : : } else {
1190 : : /*----- Non-SIMDfied print does not support SIMD. -----*/
1191 [ # # # # ]: 0 : pre_cond.add_condition(NoSIMD());
1192 : : }
1193 : :
1194 : 0 : return pre_cond;
1195 [ # # # # ]: 0 : }
1196 : :
1197 : : template<bool SIMDfied>
1198 : 0 : void Print<SIMDfied>::execute(const Match<Print> &M, setup_t, pipeline_t, teardown_t)
1199 : : {
1200 : 0 : M_insist(bool(M.result_set_factory), "`wasm::Print` must have a factory for the result set");
1201 : :
1202 [ # # # # ]: 0 : auto result_set_schema = M.print_op.schema().drop_constants().deduplicate();
1203 [ # # # # ]: 0 : write_result_set(result_set_schema, *M.result_set_factory, M.result_set_window_size, *M.child);
1204 : 0 : }
1205 : :
1206 : :
1207 : : /*======================================================================================================================
1208 : : * Scan
1209 : : *====================================================================================================================*/
1210 : :
1211 : : template<bool SIMDfied>
1212 : 0 : ConditionSet Scan<SIMDfied>::pre_condition(std::size_t child_idx,
1213 : : const std::tuple<const ScanOperator*> &partial_inner_nodes)
1214 : : {
1215 : 0 : M_insist(child_idx == 0);
1216 : :
1217 : 0 : ConditionSet pre_cond;
1218 : :
1219 : : if constexpr (SIMDfied) {
1220 : 0 : auto &scan = *std::get<0>(partial_inner_nodes);
1221 [ # # # # ]: 0 : auto &table = scan.store().table();
1222 : :
1223 : : /*----- SIMDfied scan needs the data layout to support SIMD. -----*/
1224 [ # # # # : 0 : if (not supports_simd(table.layout(), table.schema(scan.alias()), scan.schema()))
# # # # #
# # # #
# ]
1225 [ # # ]: 0 : return ConditionSet::Make_Unsatisfiable();
1226 : :
1227 : : /*----- SIMDfied scan needs the number of rows to load be a whole multiple of the number of SIMD lanes used. -*/
1228 [ # # # # : 0 : if (scan.store().num_rows() % get_num_simd_lanes(table.layout(), table.schema(scan.alias()), scan.schema()) != 0)
# # # # #
# # # # #
# # # # ]
1229 [ # # ]: 0 : return ConditionSet::Make_Unsatisfiable();
1230 : : }
1231 : :
1232 : 0 : return pre_cond;
1233 : 0 : }
1234 : :
1235 : : template<bool SIMDfied>
1236 : 0 : ConditionSet Scan<SIMDfied>::post_condition(const Match<Scan> &M)
1237 : : {
1238 : 0 : ConditionSet post_cond;
1239 : :
1240 : : /*----- Scan does not introduce predication. -----*/
1241 [ # # # # : 0 : post_cond.add_condition(Predicated(false));
# # # # ]
1242 : :
1243 : : if constexpr (SIMDfied) {
1244 : : /*----- SIMDfied scan introduces SIMD vectors with respective number of lanes. -----*/
1245 [ # # # # ]: 0 : auto &table = M.scan.store().table();
1246 [ # # # # : 0 : const auto num_simd_lanes = get_num_simd_lanes(table.layout(), table.schema(M.scan.alias()), M.scan.schema());
# # # # #
# # # ]
1247 [ # # # # ]: 0 : post_cond.add_condition(SIMD(num_simd_lanes));
1248 : : } else {
1249 : : /*----- Non-SIMDfied scan does not introduce SIMD. -----*/
1250 [ # # # # ]: 0 : post_cond.add_condition(NoSIMD());
1251 : : }
1252 : :
1253 : : /*----- Check if any attribute of scanned table is assumed to be sorted. -----*/
1254 : 0 : Sortedness::order_t orders;
1255 [ # # # # : 0 : for (auto &e : M.scan.schema()) {
# # # # #
# # # # #
# # ]
1256 : 0 : auto pred = [&e](const auto &p){ return e.id == p.first; };
1257 [ # # # # : 0 : if (auto it = std::find_if(options::sorted_attributes.cbegin(), options::sorted_attributes.cend(), pred);
# # # # #
# # # ]
1258 : 0 : it != options::sorted_attributes.cend())
1259 : : {
1260 [ # # # # : 0 : orders.add(e.id, it->second ? Sortedness::O_ASC : Sortedness::O_DESC);
# # # # ]
1261 : 0 : }
1262 : : }
1263 [ # # # # : 0 : if (not orders.empty())
# # # # ]
1264 [ # # # # : 0 : post_cond.add_condition(Sortedness(std::move(orders)));
# # # # ]
1265 : :
1266 : 0 : return post_cond;
1267 [ # # # # ]: 0 : }
1268 : :
1269 : : template<bool SIMDfied>
1270 : 10 : void Scan<SIMDfied>::execute(const Match<Scan> &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
1271 : : {
1272 : 10 : auto &schema = M.scan.schema();
1273 : 10 : auto &table = M.scan.store().table();
1274 : :
1275 [ + - + - : 10 : M_insist(schema == schema.drop_constants().deduplicate(), "schema of `ScanOperator` must not contain NULL or duplicates");
+ - # # #
# # # ]
1276 : 10 : M_insist(not table.layout().is_finite(), "layout for `wasm::Scan` must be infinite");
1277 : :
1278 : 10 : Var<U32x1> tuple_id; // default initialized to 0
1279 : :
1280 : : /*----- Compute possible number of SIMD lanes and decide which to use with regard to other operators preferences. */
1281 [ + - + - : 10 : const auto layout_schema = table.schema(M.scan.alias());
+ - # # #
# # # ]
1282 [ + - + - : 10 : CodeGenContext::Get().update_num_simd_lanes_preferred(options::simd_lanes); // set configured preference
# # # # ]
1283 : 10 : const auto num_simd_lanes_preferred =
1284 [ + - + - : 10 : CodeGenContext::Get().num_simd_lanes_preferred(); // get other operators preferences
# # # # ]
1285 : 10 : const std::size_t num_simd_lanes =
1286 : 0 : SIMDfied ? (options::double_pumping ? 2 : 1)
1287 [ # # # # : 0 : * std::max(num_simd_lanes_preferred, get_num_simd_lanes(table.layout(), layout_schema, schema))
# # ]
1288 : : : 1;
1289 [ + - + - : 10 : CodeGenContext::Get().set_num_simd_lanes(num_simd_lanes);
# # # # ]
1290 : :
1291 : : /*----- Import the number of rows of `table`. -----*/
1292 [ + - + - : 10 : U32x1 num_rows = get_num_rows(table.name());
# # # # ]
1293 : :
1294 : : /*----- If no attributes must be loaded, generate a loop just executing the pipeline `num_rows`-times. -----*/
1295 [ + - - + : 10 : if (schema.num_entries() == 0) {
# # # # ]
1296 [ # # # # ]: 0 : setup();
1297 [ # # # # : 0 : WHILE (tuple_id < num_rows) {
# # # # #
# # # # #
# # ]
1298 [ # # # # ]: 0 : tuple_id += uint32_t(num_simd_lanes);
1299 [ # # # # ]: 0 : pipeline();
1300 : : }
1301 [ # # # # ]: 0 : teardown();
1302 : 0 : return;
1303 : : }
1304 : :
1305 : : /*----- Import the base address of the mapped memory. -----*/
1306 [ + - + - : 10 : Ptr<void> base_address = get_base_address(table.name());
# # # # ]
1307 : :
1308 : : /*----- Emit setup code *before* compiling data layout to not overwrite its temporary boolean variables. -----*/
1309 [ + - # # ]: 10 : setup();
1310 : :
1311 : : /*----- Compile data layout to generate sequential load from table. -----*/
1312 [ + + - + : 10 : static Schema empty_schema;
# # # # ]
1313 [ + - + - : 30 : auto [inits, loads, jumps] = compile_load_sequential(schema, empty_schema, base_address, table.layout(),
+ - # # #
# # # ]
1314 : 0 : num_simd_lanes, layout_schema, tuple_id);
1315 : :
1316 : : /*----- Generate the loop for the actual scan, with the pipeline emitted into the loop body. -----*/
1317 [ + - # # ]: 10 : inits.attach_to_current();
1318 [ + - + - : 20 : WHILE (tuple_id < num_rows) {
+ - + - #
# # # # #
# # ]
1319 [ + - # # ]: 10 : loads.attach_to_current();
1320 [ + - # # ]: 10 : pipeline();
1321 [ + - # # ]: 10 : jumps.attach_to_current();
1322 : : }
1323 : :
1324 : : /*----- Emit teardown code. -----*/
1325 [ + - # # ]: 10 : teardown();
1326 [ - + # # ]: 10 : }
1327 : :
1328 : :
1329 : : /*======================================================================================================================
1330 : : * Index Scan
1331 : : *====================================================================================================================*/
1332 : :
1333 : : template<idx::IndexMethod IndexMethod>
1334 : 0 : ConditionSet IndexScan<IndexMethod>::pre_condition(std::size_t child_idx,
1335 : : const std::tuple<const FilterOperator*,
1336 : : const ScanOperator*> &partial_inner_nodes)
1337 : : {
1338 : 0 : M_insist(child_idx == 0);
1339 : :
1340 : : /*----- Check if index on one of the attributes in filter condition exists. -----*/
1341 : 0 : auto &filter = *std::get<0>(partial_inner_nodes);
1342 : 0 : auto &cnf = filter.filter();
1343 : :
1344 : 0 : M_insist(not cnf.empty(), "Filter condition must not be empty");
1345 : :
1346 : 0 : auto &scan = *std::get<1>(partial_inner_nodes);
1347 : 0 : auto &table = scan.store().table();
1348 : :
1349 : 0 : Catalog &C = Catalog::Get();
1350 : 0 : auto &DB = C.get_database_in_use();
1351 : :
1352 : : /* Extract attributes from filter condition. */
1353 : 0 : std::vector<Schema::Identifier> ids;
1354 [ # # # # : 0 : for (auto &entry : cnf.get_required()) {
# # # # #
# # # # #
# # ]
1355 : 0 : Schema::Identifier &id = entry.id;
1356 [ # # # # : 0 : M_insist(table.name() == id.prefix, "Table name should match designator table name");
# # # # #
# # # # #
# # ]
1357 : :
1358 : : /* Keep attributes for which an index exists. */
1359 [ # # # # : 0 : if (DB.has_index(table.name(), id.name, IndexMethod))
# # # # #
# # # ]
1360 [ # # # # ]: 0 : ids.push_back(id);
1361 : : }
1362 [ # # # # ]: 0 : if (ids.empty()) // no usable index found
1363 [ # # # # ]: 0 : return ConditionSet::Make_Unsatisfiable();
1364 : :
1365 : : /*----- Check if filter condition is supported. -----*/
1366 : : /* We currently only support filter conditions of the following form.
1367 : : * 1. Point: condition with a single equality predicate, e.g.
1368 : : * x = 42.
1369 : : * 2. One-sided range: condition with a single greater/greater-or-equal/less/less-or-equal predicate, e.g.
1370 : : * x > 42.
1371 : : * 3. Two-sided range: condition with a greater/greater-or-equal predicate and a less/less-or-equal predicate, e.g.
1372 : : * x > 42 AND x <= 89.
1373 : : * Attributes may appear on either side. The other side is required to be a `Constant`. */
1374 [ # # # # ]: 0 : if (ids.size() > 1) // conditions with more than one attribute currently not supported
1375 [ # # # # ]: 0 : return ConditionSet::Make_Unsatisfiable();
1376 : :
1377 : 0 : bool has_lo_bound = false;
1378 : 0 : bool has_hi_bound = false;
1379 [ # # # # ]: 0 : for (auto &clause : cnf) {
1380 [ # # # # ]: 0 : if (clause.size() != 1) // disjunctions not supported
1381 [ # # # # ]: 0 : return ConditionSet::Make_Unsatisfiable();
1382 : :
1383 : 0 : auto &predicate = clause[0];
1384 [ # # # # : 0 : if (predicate.negative()) // negated predicates not supported
# # # # ]
1385 [ # # # # ]: 0 : return ConditionSet::Make_Unsatisfiable();
1386 : :
1387 [ # # # # : 0 : auto expr = cast<const BinaryExpr>(&predicate.expr());
# # # # ]
1388 [ # # # # ]: 0 : if (not expr) // not a binary expression
1389 [ # # # # ]: 0 : return ConditionSet::Make_Unsatisfiable();
1390 : :
1391 [ # # # # ]: 0 : bool has_attribute_left = is<const Designator>(expr->lhs);
1392 [ # # # # ]: 0 : auto &attribute = has_attribute_left ? *expr->lhs : *expr->rhs;
1393 [ # # # # ]: 0 : auto &constant = has_attribute_left ? *expr->rhs : *expr->lhs;
1394 [ # # # # : 0 : if (not is<const Designator>(attribute)) // attribute must be on lhs
# # # # ]
1395 [ # # # # ]: 0 : return ConditionSet::Make_Unsatisfiable();
1396 [ # # # # : 0 : if (not is_valid_bound(constant))
# # # # ]
1397 [ # # # # ]: 0 : return ConditionSet::Make_Unsatisfiable();
1398 : :
1399 [ # # # # : 0 : switch(expr->tok.type) {
# # # # ]
1400 : : default:
1401 [ # # # # ]: 0 : return ConditionSet::Make_Unsatisfiable();
1402 : : case TK_EQUAL:
1403 [ # # # # : 0 : if (not has_lo_bound and not has_hi_bound) { // lo and hi bound not yet set
# # # # ]
1404 : 0 : has_lo_bound = has_hi_bound = true;
1405 : 0 : } else {
1406 [ # # # # ]: 0 : return ConditionSet::Make_Unsatisfiable();
1407 : : }
1408 : 0 : break;
1409 : : case TK_GREATER:
1410 : : case TK_GREATER_EQUAL:
1411 [ # # # # : 0 : if (has_attribute_left and not has_lo_bound) { // attribute on lhs, lo bound not yet set
# # # # ]
1412 : 0 : has_lo_bound = true;
1413 [ # # # # : 0 : } else if (not has_attribute_left and not has_hi_bound) { // attribute on rhs, hi bound not yet set
# # # # ]
1414 : 0 : has_hi_bound = true;
1415 : 0 : } else {
1416 [ # # # # ]: 0 : return ConditionSet::Make_Unsatisfiable();
1417 : : }
1418 : 0 : break;
1419 : : case TK_LESS:
1420 : : case TK_LESS_EQUAL:
1421 [ # # # # : 0 : if (has_attribute_left and not has_hi_bound) { // attribute on lhs, hi bound not yet set
# # # # ]
1422 : 0 : has_hi_bound = true;
1423 [ # # # # ]: 0 : } else if (not has_attribute_left and not has_lo_bound) { // attribute on rhs, lo bound not yet set
1424 : 0 : has_lo_bound = true;
1425 : 0 : } else {
1426 [ # # # # ]: 0 : return ConditionSet::Make_Unsatisfiable();
1427 : : }
1428 : 0 : break;
1429 : : }
1430 : : }
1431 : 0 : return ConditionSet();
1432 : 0 : }
1433 : :
1434 : : template<idx::IndexMethod IndexMethod>
1435 : 0 : ConditionSet IndexScan<IndexMethod>::post_condition(const Match<IndexScan<IndexMethod>> &M)
1436 : : {
1437 : 0 : ConditionSet post_cond;
1438 : :
1439 : : /*----- Index scan does not introduce predication. -----*/
1440 [ # # # # : 0 : post_cond.add_condition(Predicated(false));
# # # # ]
1441 : :
1442 : : /*----- Non-SIMDfied index scan does not introduce SIMD. -----*/
1443 [ # # # # : 0 : post_cond.add_condition(NoSIMD());
# # # # ]
1444 : :
1445 : : /*----- Index scan introduces sortedness on indexed attribute. -----*/
1446 : : /* Extract identifier from cnf. */
1447 [ # # # # ]: 0 : auto &cnf = M.filter.filter();
1448 [ # # # # ]: 0 : Schema designators = cnf.get_required();
1449 [ # # # # : 0 : M_insist(designators.num_entries() == 1, "filter condition must contain exactly one designator");
# # # # ]
1450 [ # # # # ]: 0 : Schema::Identifier &id = designators[0].id;
1451 : :
1452 : : /* Add sortedness post condition. */
1453 : 0 : Sortedness::order_t orders;
1454 [ # # # # : 0 : orders.add(id, Sortedness::O_ASC);
# # # # ]
1455 [ # # # # : 0 : post_cond.add_condition(Sortedness(std::move(orders)));
# # # # ]
1456 : :
1457 : 0 : return post_cond;
1458 [ # # # # ]: 0 : }
1459 : :
1460 : : template<idx::IndexMethod IndexMethod>
1461 : 0 : double IndexScan<IndexMethod>::cost(const Match<IndexScan<IndexMethod>>&)
1462 : : {
1463 : : /* TODO: add meaningful cost function. */
1464 : 0 : return 0.0;
1465 : : }
1466 : :
1467 : : template<idx::IndexMethod IndexMethod, typename Index, sql_type SqlT>
1468 : 0 : void index_scan_codegen_compilation(const Index &index, const index_scan_bounds_t &bounds,
1469 : : const Match<IndexScan<IndexMethod>> &M,
1470 : : setup_t setup, pipeline_t pipeline, teardown_t teardown) {
1471 : : using key_type = Index::key_type;
1472 : : using sql_type = SqlT;
1473 : :
1474 [ # # # # : 0 : if (options::index_scan_compilation_strategy == option_configs::IndexScanCompilationStrategy::CALLBACK) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1475 : : /*----- Resolve callback function names. -----*/
1476 : : const char *scan_fn, *lower_bound_fn, *upper_bound_fn;
1477 : : #define SET_CALLBACK_FNS(INDEX, KEY) \
1478 : : scan_fn = M_STR(idx_scan_##INDEX##_##KEY); \
1479 : : lower_bound_fn = M_STR(idx_lower_bound_##INDEX##_##KEY); \
1480 : : upper_bound_fn = M_STR(idx_upper_bound_##INDEX##_##KEY)
1481 : :
1482 : : #define RESOLVE_KEYTYPE(INDEX) \
1483 : : if constexpr(std::same_as<SqlT, _Boolx1>) { \
1484 : : SET_CALLBACK_FNS(INDEX, b); \
1485 : : } else if constexpr(std::same_as<sql_type, _I8x1>) { \
1486 : : SET_CALLBACK_FNS(INDEX, i1); \
1487 : : } else if constexpr(std::same_as<sql_type, _I16x1>) { \
1488 : : SET_CALLBACK_FNS(INDEX, i2); \
1489 : : } else if constexpr(std::same_as<sql_type, _I32x1>) { \
1490 : : SET_CALLBACK_FNS(INDEX, i4); \
1491 : : } else if constexpr(std::same_as<sql_type, _I64x1>) { \
1492 : : SET_CALLBACK_FNS(INDEX, i8); \
1493 : : } else if constexpr(std::same_as<sql_type, _Floatx1>) { \
1494 : : SET_CALLBACK_FNS(INDEX, f); \
1495 : : } else if constexpr(std::same_as<sql_type, _Doublex1>) { \
1496 : : SET_CALLBACK_FNS(INDEX, d); \
1497 : : } else if constexpr(std::same_as<sql_type, NChar>) { \
1498 : : SET_CALLBACK_FNS(IDNEX, p); \
1499 : : } else { \
1500 : : M_unreachable("incompatible SQL type"); \
1501 : : }
1502 : : if constexpr(is_specialization<Index, idx::ArrayIndex>) {
1503 : 0 : RESOLVE_KEYTYPE(array)
1504 : : } else if constexpr(is_specialization<Index, idx::RecursiveModelIndex>) {
1505 : 0 : RESOLVE_KEYTYPE(rmi)
1506 : : } else {
1507 : : M_unreachable("unknown index type");
1508 : : }
1509 : : #undef RESOLVE_KEYTYPE
1510 : : #undef SET_CALLBACK_FNS
1511 : :
1512 : : /*----- Add index to context. -----*/
1513 : 0 : auto &context = WasmEngine::Get_Wasm_Context_By_ID(Module::ID());
1514 : 0 : U64x1 index_id(context.add_index(index));
1515 : :
1516 : : /*----- Emit host calls to query the index for lo and hi bounds. -----*/
1517 : 0 : auto compile_bound_lookup = [&](const ast::Expr &bound, bool is_lower_bound) {
1518 : 0 : auto [constant, is_negative] = get_valid_bound(bound);
1519 : 0 : auto c = Interpreter::eval(constant);
1520 : : key_type _key;
1521 : : if constexpr(m::boolean<key_type>) {
1522 : 0 : _key = bool(c);
1523 : 0 : M_insist(not is_negative, "boolean cannot be negative");
1524 : : } else if constexpr(m::integral<key_type>) {
1525 : 0 : auto i64 = int64_t(c);
1526 : 0 : M_insist(std::in_range<key_type>(i64), "integeral constant must be in range");
1527 : 0 : _key = key_type(i64);
1528 [ # # # # : 0 : _key = is_negative ? -_key : _key;
# # # # #
# # # # #
# # ]
1529 : : } else if constexpr(std::same_as<float, key_type>) {
1530 : 0 : auto d = double(c);
1531 : 0 : _key = key_type(d);
1532 : 0 : M_insist(_key == d, "downcasting should not impact precision");
1533 [ # # # # ]: 0 : _key = is_negative ? -_key : _key;
1534 : : } else if constexpr(std::same_as<double, key_type>) {
1535 : 0 : _key = double(c);
1536 [ # # # # ]: 0 : _key = is_negative ? -_key : _key;
1537 : : } else if constexpr(std::same_as<const char*, key_type>) {
1538 : 0 : _key = reinterpret_cast<const char*>(c.as_p());
1539 : 0 : M_insist(not is_negative, "string cannot be negative");
1540 : : }
1541 : :
1542 : 0 : std::optional<typename sql_type::primitive_type> key;
1543 [ # # # # : 0 : if (options::index_scan_materialization_strategy == option_configs::IndexScanMaterializationStrategy::INLINE) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1544 : : if constexpr (std::same_as<sql_type, NChar>) {
1545 [ # # # # : 0 : key.emplace(CodeGenContext::Get().get_literal_address(_key));
# # ]
1546 : : } else {
1547 [ # # # # : 0 : key.emplace(_key);
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
1548 : : }
1549 [ # # # # : 0 : } else if (options::index_scan_materialization_strategy == option_configs::IndexScanMaterializationStrategy::MEMORY) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1550 : : /* If we materialize before calling the bound functions, the key parameter is independent of the bounds.
1551 : : * As a result, queries that only differ in the filter predicate are compiled to the exact same
1552 : : * WebAssembly code, enabling caching of compiled plans in V8. */
1553 : : if constexpr (std::same_as<sql_type, NChar>) {
1554 [ # # # # ]: 0 : uint32_t *key_address = Module::Allocator().raw_malloc<uint32_t>();
1555 [ # # # # ]: 0 : *key_address = CodeGenContext::Get().get_literal_raw_address(_key);
1556 : :
1557 [ # # ]: 0 : Ptr<U32x1> key_ptr(key_address);
1558 [ # # # # : 0 : key.emplace(U32x1(*key_ptr).to<char*>(), false, as<const CharacterSequence>(bound.type()));
# # # # #
# # # ]
1559 : 0 : } else {
1560 [ # # # # : 0 : auto *key_address = Module::Allocator().raw_malloc<typename sql_type::type>();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
1561 : 0 : *key_address = _key;
1562 : :
1563 [ # # # # : 0 : Ptr<typename sql_type::primitive_type> key_ptr(key_address);
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
1564 [ # # # # : 0 : key.emplace(*key_ptr);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
1565 : 0 : }
1566 : 0 : } else {
1567 [ # # # # : 0 : M_unreachable("unknown materialization strategy");
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1568 : : }
1569 [ # # # # : 0 : M_insist(bool(key), "key must be set");
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1570 [ # # # # : 0 : return Module::Get().emit_call<uint32_t>(
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
1571 [ # # # # : 0 : /* fn= */ is_lower_bound ? lower_bound_fn : upper_bound_fn,
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1572 [ # # # # : 0 : /* index_id= */ index_id.clone(),
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1573 [ # # # # : 0 : /* key= */ *key
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1574 : : );
1575 : 0 : };
1576 [ # # # # : 0 : Var<U32x1> lo(bool(bounds.lo) ? compile_bound_lookup(bounds.lo->get(), bounds.is_inclusive_lo)
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
1577 [ # # # # : 0 : : U32x1(0));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1578 [ # # # # : 0 : const Var<U32x1> hi(bool(bounds.hi) ? compile_bound_lookup(bounds.hi->get(), not bounds.is_inclusive_hi)
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
1579 [ # # # # : 0 : : U32x1(index.num_entries()));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
1580 [ # # # # : 0 : Wasm_insist(lo <= hi, "bounds need to be valid");
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
1581 : :
1582 : : /*----- Allocate memory for communication to host. -----*/
1583 [ # # # # : 0 : M_insist(std::in_range<uint32_t>(M.batch_size), "should fit in uint32_t");
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1584 : :
1585 : : /* Determine alloc size as minimum of number of results and command-line parameter batch size, where 0 is
1586 : : * interpreted as infinity. */
1587 [ # # # # : 0 : const Var<U32x1> alloc_size([&](){
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
1588 : 0 : U32x1 num_results = hi - lo;
1589 [ # # # # : 0 : U32x1 num_results_cpy = num_results.clone();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1590 [ # # # # : 0 : U32x1 batch_size = M.batch_size == 0 ? num_results.clone() : U32x1(M.batch_size);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
1591 [ # # # # : 0 : U32x1 batch_size_cpy = batch_size.clone();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1592 [ # # # # : 0 : return Select(batch_size < num_results, batch_size_cpy, num_results_cpy);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
1593 : 0 : }());
1594 [ # # # # : 0 : Ptr<U32x1> buffer_address = Module::Allocator().malloc<uint32_t>(alloc_size);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
1595 : :
1596 : : /*----- Emit setup code *after* allocating memory to guarantee sequential memory allocation for pipeline. -----*/
1597 [ # # # # : 0 : setup();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1598 : :
1599 : : /*----- Emit loop code. -----*/
1600 [ # # # # : 0 : Var<U32x1> num_tuples_in_batch;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1601 [ # # # # : 0 : Var<Ptr<U32x1>> ptr;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1602 [ # # # # : 0 : WHILE (lo < hi) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
1603 [ # # # # : 0 : num_tuples_in_batch = Select(hi - lo > alloc_size, alloc_size, hi - lo);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
1604 : : /* Call host to fill buffer memory with next batch of tuple ids. */
1605 [ # # # # : 0 : Module::Get().emit_call<void>(
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
1606 : 0 : /* fn= */ scan_fn,
1607 [ # # # # : 0 : /* index_id= */ index_id,
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1608 [ # # # # : 0 : /* entry_offset= */ lo.val(),
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1609 [ # # # # : 0 : /* address= */ buffer_address.clone(),
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1610 [ # # # # : 0 : /* batch_size= */ num_tuples_in_batch.val()
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1611 : : );
1612 [ # # # # : 0 : lo += num_tuples_in_batch;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1613 [ # # # # : 0 : ptr = buffer_address.clone();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
1614 [ # # # # : 0 : WHILE(num_tuples_in_batch > 0U) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
1615 [ # # # # : 0 : static Schema empty_schema;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
1616 [ # # # # : 0 : compile_load_point_access(
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1617 : 0 : /* tuple_value_schema= */ M.scan.schema(),
1618 : : /* tuple_address_schema= */ empty_schema,
1619 [ # # # # : 0 : /* base_address= */ get_base_address(M.scan.store().table().name()),
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
1620 [ # # # # : 0 : /* layout= */ M.scan.store().table().layout(),
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1621 [ # # # # : 0 : /* layout_schema= */ M.scan.store().table().schema(M.scan.alias()),
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
1622 [ # # # # : 0 : /* tuple_id= */ *ptr
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
1623 : : );
1624 [ # # # # : 0 : pipeline();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1625 [ # # # # : 0 : num_tuples_in_batch -= 1U;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1626 [ # # # # : 0 : ptr += 1;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1627 : : }
1628 : : }
1629 : :
1630 : : /*----- Emit teardown code. -----*/
1631 [ # # # # : 0 : teardown();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1632 : :
1633 : : /*----- Free buffer memory. -----*/
1634 [ # # # # : 0 : IF (alloc_size > U32x1(0)) { // only free if actually allocated
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
1635 : 0 : Module::Allocator().free(buffer_address, alloc_size);
1636 : 0 : };
1637 [ # # # # : 0 : } else if (options::index_scan_compilation_strategy == option_configs::IndexScanCompilationStrategy::EXPOSED_MEMORY) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1638 : 0 : M_unreachable("not implemented yet");
1639 : : } else {
1640 : 0 : M_unreachable("unknown compilation stategy");
1641 : : }
1642 : 0 : }
1643 : :
1644 : : template<idx::IndexMethod IndexMethod, typename Index>
1645 : 0 : void index_scan_codegen_interpretation(const Index &index, const index_scan_bounds_t &bounds,
1646 : : const Match<IndexScan<IndexMethod>> &M,
1647 : : setup_t setup, pipeline_t pipeline, teardown_t teardown)
1648 : : {
1649 : : using key_type = Index::key_type;
1650 : :
1651 [ # # # # : 0 : static Schema empty_schema;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
1652 : :
1653 : : /*----- Interpret lo and hi bounds to retrieve index scan range -----*/
1654 : 0 : auto interpret_and_lookup_bound = [&](const ast::Expr &bound, bool is_lower_bound) -> std::size_t {
1655 : 0 : auto [constant, is_negative] = get_valid_bound(bound);
1656 : 0 : auto c = Interpreter::eval(constant);
1657 : : key_type key;
1658 : : if constexpr(m::boolean<key_type>) {
1659 : 0 : key = bool(c);
1660 : 0 : M_insist(not is_negative, "boolean cannot be negative");
1661 : : } else if constexpr(m::integral<key_type>) {
1662 : 0 : auto i64 = int64_t(c);
1663 : 0 : M_insist(std::in_range<key_type>(i64), "integeral constant must be in range");
1664 : 0 : key = key_type(i64);
1665 [ # # # # : 0 : key = is_negative ? -key : key;
# # # # #
# # # # #
# # ]
1666 : : } else if constexpr(std::same_as<float, key_type>) {
1667 : 0 : auto d = double(c);
1668 : 0 : key = key_type(d);
1669 : 0 : M_insist(key == d, "downcasting should not impact precision");
1670 [ # # # # ]: 0 : key = is_negative ? -key : key;
1671 : : } else if constexpr(std::same_as<double, key_type>) {
1672 : 0 : key = double(c);
1673 [ # # # # ]: 0 : key = is_negative ? -key : key;
1674 : : } else if constexpr(std::same_as<const char*, key_type>) {
1675 : 0 : key = reinterpret_cast<const char*>(c.as_p());
1676 : 0 : M_insist(not is_negative, "string cannot be negative");
1677 : : }
1678 [ # # # # : 0 : return std::distance(index.begin(), is_lower_bound ? index.lower_bound(key)
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1679 : 0 : : index.upper_bound(key));
1680 : : };
1681 [ # # # # : 0 : std::size_t lo = bool(bounds.lo) ? interpret_and_lookup_bound(bounds.lo->get(), bounds.is_inclusive_lo)
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1682 : : : 0UL;
1683 [ # # # # : 0 : std::size_t hi = bool(bounds.hi) ? interpret_and_lookup_bound(bounds.hi->get(), not bounds.is_inclusive_hi)
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1684 : 0 : : index.num_entries();
1685 : 0 : M_insist(lo <= hi, "bounds need to be valid");
1686 : :
1687 [ # # # # : 0 : if (options::index_scan_materialization_strategy == option_configs::IndexScanMaterializationStrategy::MEMORY) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1688 : : /*----- Allocate sufficient memory for results. -----*/
1689 : 0 : uint32_t num_results = hi - lo;
1690 : 0 : uint32_t *buffer_address = Module::Allocator().raw_malloc<uint32_t>(num_results + 1); // +1 for storing number of results itself
1691 : :
1692 : : /*----- Perform index scan and fill memory with number of results and results. -----*/
1693 : 0 : uint32_t *buffer_ptr = buffer_address;
1694 : 0 : *buffer_ptr = num_results; // store in memory to enable caching
1695 : 0 : ++buffer_ptr;
1696 [ # # # # : 0 : for (auto it = index.begin() + lo; it != index.begin() + hi; ++it) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1697 : 0 : M_insist(std::in_range<uint32_t>(it->second), "tuple id must fit in uint32_t");
1698 : 0 : *buffer_ptr = it->second;
1699 : 0 : ++buffer_ptr;
1700 : 0 : }
1701 : :
1702 : : /*----- Emit setup code *after* allocating memory to guarantee sequential memory allocation for pipeline. -----*/
1703 : 0 : setup();
1704 : :
1705 : : /*----- Emit loop code. -----*/
1706 : 0 : Ptr<U32x1> base(buffer_address + 1); // +1 to skip stored number of results
1707 [ # # # # : 0 : Var<Ptr<U32x1>> ptr(base.clone());
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
1708 [ # # # # : 0 : const Var<Ptr<U32x1>> end(base + U32x1(*Ptr<U32x1>(buffer_address)).make_signed());
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1709 [ # # # # : 0 : WHILE(ptr < end) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
1710 [ # # # # : 0 : compile_load_point_access(
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1711 : 0 : /* tuple_value_schema= */ M.scan.schema(),
1712 : : /* tuple_address_schema= */ empty_schema,
1713 [ # # # # : 0 : /* base_address= */ get_base_address(M.scan.store().table().name()),
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
1714 [ # # # # : 0 : /* layout= */ M.scan.store().table().layout(),
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1715 [ # # # # : 0 : /* layout_schema= */ M.scan.store().table().schema(M.scan.alias()),
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
1716 [ # # # # : 0 : /* tuple_id= */ *ptr
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
1717 : : );
1718 [ # # # # : 0 : pipeline();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1719 [ # # # # : 0 : ptr += 1;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1720 : : }
1721 : :
1722 : : /*----- Emit teardown code. -----*/
1723 [ # # # # : 0 : teardown();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1724 [ # # # # : 0 : } else if (options::index_scan_materialization_strategy == option_configs::IndexScanMaterializationStrategy::INLINE) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1725 : : /*----- Define function that emits code for loading and executing pipeline for a single tuple. -----*/
1726 [ # # # # : 0 : FUNCTION(index_scan_parent_pipeline, void(uint32_t))
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1727 : : {
1728 [ # # # # : 0 : auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
1729 : :
1730 : : /*----- Emit setup code. -----*/
1731 [ # # # # : 0 : setup();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1732 : :
1733 : : /*----- Load tuple. ----- */
1734 [ # # # # : 0 : compile_load_point_access(
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1735 : 0 : /* tuple_value_schema= */ M.scan.schema(),
1736 : : /* tuple_address_schema= */ empty_schema,
1737 [ # # # # : 0 : /* base_address= */ get_base_address(M.scan.store().table().name()),
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
1738 [ # # # # : 0 : /* layout= */ M.scan.store().table().layout(),
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1739 [ # # # # : 0 : /* layout_schema= */ M.scan.store().table().schema(M.scan.alias()),
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
1740 [ # # # # : 0 : /* tuple_id= */ PARAMETER(0)
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
1741 : : );
1742 : :
1743 : : /*----- Emit pipeline code. -----*/
1744 [ # # # # : 0 : pipeline();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1745 : :
1746 : : /*----- Emit teardown code. -----*/
1747 [ # # # # : 0 : teardown();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1748 : 0 : }
1749 : :
1750 : : /*----- Perform index sequential scan, emit code to execute pipeline for each tuple. -----*/
1751 [ # # # # : 0 : for (auto it = index.begin() + lo; it != index.begin() + hi; ++it) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
1752 [ # # # # : 0 : M_insist(std::in_range<uint32_t>(it->second), "tuple id must fit in uint32_t");
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1753 [ # # # # : 0 : index_scan_parent_pipeline(uint32_t(it->second));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1754 : 0 : }
1755 : 0 : } else {
1756 : 0 : M_unreachable("unknown materialization strategy");
1757 : : }
1758 : 0 : }
1759 : :
1760 : : template<idx::IndexMethod IndexMethod, typename Index, sql_type SqlT>
1761 : 0 : void index_scan_codegen_hybrid(const Index &index, const index_scan_bounds_t &bounds,
1762 : : const Match<IndexScan<IndexMethod>> &M,
1763 : : setup_t setup, pipeline_t pipeline, teardown_t teardown)
1764 : : {
1765 : : using key_type = Index::key_type;
1766 : : using sql_type = SqlT;
1767 : :
1768 : : /*----- Resolve callback function name. -----*/
1769 : : const char *scan_fn;
1770 : : #define SET_CALLBACK_FNS(INDEX, KEY) \
1771 : : scan_fn = M_STR(idx_scan_##INDEX##_##KEY)
1772 : :
1773 : : #define RESOLVE_KEYTYPE(INDEX) \
1774 : : if constexpr(std::same_as<SqlT, _Boolx1>) { \
1775 : : SET_CALLBACK_FNS(INDEX, b); \
1776 : : } else if constexpr(std::same_as<sql_type, _I8x1>) { \
1777 : : SET_CALLBACK_FNS(INDEX, i1); \
1778 : : } else if constexpr(std::same_as<sql_type, _I16x1>) { \
1779 : : SET_CALLBACK_FNS(INDEX, i2); \
1780 : : } else if constexpr(std::same_as<sql_type, _I32x1>) { \
1781 : : SET_CALLBACK_FNS(INDEX, i4); \
1782 : : } else if constexpr(std::same_as<sql_type, _I64x1>) { \
1783 : : SET_CALLBACK_FNS(INDEX, i8); \
1784 : : } else if constexpr(std::same_as<sql_type, _Floatx1>) { \
1785 : : SET_CALLBACK_FNS(INDEX, f); \
1786 : : } else if constexpr(std::same_as<sql_type, _Doublex1>) { \
1787 : : SET_CALLBACK_FNS(INDEX, d); \
1788 : : } else if constexpr(std::same_as<sql_type, NChar>) { \
1789 : : SET_CALLBACK_FNS(IDNEX, p); \
1790 : : } else { \
1791 : : M_unreachable("incompatible SQL type"); \
1792 : : }
1793 : : if constexpr(is_specialization<Index, idx::ArrayIndex>) {
1794 : 0 : RESOLVE_KEYTYPE(array)
1795 : : } else if constexpr(is_specialization<Index, idx::RecursiveModelIndex>) {
1796 : 0 : RESOLVE_KEYTYPE(rmi)
1797 : : } else {
1798 : : M_unreachable("unknown index type");
1799 : : }
1800 : : #undef RESOLVE_KEYTYPE
1801 : : #undef SET_CALLBACK_FNS
1802 : :
1803 : : /*----- Add index to context. -----*/
1804 : 0 : auto &context = WasmEngine::Get_Wasm_Context_By_ID(Module::ID());
1805 : 0 : U64x1 index_id(context.add_index(index)); ///< id of index in context
1806 : :
1807 : : /*----- Interpret lo and hi bounds to retrieve index scan range -----*/
1808 : 0 : auto interpret_and_lookup_bound = [&](const ast::Expr &bound, bool is_lower_bound) -> std::size_t {
1809 : 0 : auto [constant, is_negative] = get_valid_bound(bound);
1810 : 0 : auto c = Interpreter::eval(constant);
1811 : : key_type key;
1812 : : if constexpr(m::boolean<key_type>) {
1813 : 0 : key = bool(c);
1814 : 0 : M_insist(not is_negative, "boolean cannot be negative");
1815 : : } else if constexpr(m::integral<key_type>) {
1816 : 0 : auto i64 = int64_t(c);
1817 : 0 : M_insist(std::in_range<key_type>(i64), "integeral constant must be in range");
1818 : 0 : key = key_type(i64);
1819 [ # # # # : 0 : key = is_negative ? -key : key;
# # # # #
# # # # #
# # ]
1820 : : } else if constexpr(std::same_as<float, key_type>) {
1821 : 0 : auto d = double(c);
1822 : 0 : key = key_type(d);
1823 : 0 : M_insist(key == d, "downcasting should not impact precision");
1824 [ # # # # ]: 0 : key = is_negative ? -key : key;
1825 : : } else if constexpr(std::same_as<double, key_type>) {
1826 : 0 : key = double(c);
1827 [ # # # # ]: 0 : key = is_negative ? -key : key;
1828 : : } else if constexpr(std::same_as<const char*, key_type>) {
1829 : 0 : key = reinterpret_cast<const char*>(c.as_p());
1830 : 0 : M_insist(not is_negative, "string cannot be negative");
1831 : : }
1832 [ # # # # : 0 : return std::distance(index.begin(), is_lower_bound ? index.lower_bound(key)
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1833 : 0 : : index.upper_bound(key));
1834 : : };
1835 [ # # # # : 0 : std::size_t lo = bool(bounds.lo) ? interpret_and_lookup_bound(bounds.lo->get(), bounds.is_inclusive_lo)
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
1836 : : : 0UL;
1837 [ # # # # : 0 : std::size_t hi = bool(bounds.hi) ? interpret_and_lookup_bound(bounds.hi->get(), not bounds.is_inclusive_hi)
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
1838 [ # # # # : 0 : : index.num_entries();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1839 [ # # # # : 0 : M_insist(lo <= hi, "bounds need to be valid");
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1840 [ # # # # : 0 : M_insist(std::in_range<uint32_t>(lo), "should fit in uint32_t");
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1841 [ # # # # : 0 : M_insist(std::in_range<uint32_t>(hi), "should fit in uint32_t");
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1842 : :
1843 : : /*----- Materialize offsets hi and lo. -----*/
1844 [ # # # # : 0 : Var<U32x1> begin;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1845 : 0 : std::optional<U32x1> end;
1846 [ # # # # : 0 : if (options::index_scan_materialization_strategy == option_configs::IndexScanMaterializationStrategy::INLINE) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1847 [ # # # # : 0 : begin = U32x1(lo);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
1848 [ # # # # : 0 : end.emplace(hi);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1849 [ # # # # : 0 : } else if (options::index_scan_materialization_strategy == option_configs::IndexScanMaterializationStrategy::MEMORY) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1850 : : /* If we materialize before allocating buffer memory, the addresses are independent of the buffer size. As a
1851 : : * result, queries that only differ in the filter predicate are compiled to the exact same WebAssembly code,
1852 : : * enabling caching of compiled plans in V8. */
1853 [ # # # # : 0 : uint32_t *offset_address = Module::Allocator().raw_malloc<uint32_t>(2);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
1854 : 0 : offset_address[0] = uint32_t(lo);
1855 : 0 : offset_address[1] = uint32_t(hi);
1856 : :
1857 [ # # # # : 0 : Ptr<U32x1> offset_ptr(offset_address);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1858 [ # # # # : 0 : begin = *offset_ptr.clone();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
1859 [ # # # # : 0 : end.emplace(*(offset_ptr + 1));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
1860 : 0 : } else {
1861 [ # # # # : 0 : M_unreachable("unknown materialization strategy");
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1862 : : }
1863 [ # # # # : 0 : M_insist(bool(end), "end must be set");
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1864 : :
1865 [ # # # # : 0 : if (options::index_scan_compilation_strategy == option_configs::IndexScanCompilationStrategy::CALLBACK) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1866 : : /*----- Allocate buffer memory for communication to host. -----*/
1867 [ # # # # : 0 : M_insist(std::in_range<uint32_t>(M.batch_size), "should fit in uint32_t");
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1868 : :
1869 : : /* Determine alloc size as minimum of number of results and command-line parameter batch size, where 0 is
1870 : : * interpreted as infinity. */
1871 [ # # # # : 0 : const Var<U32x1> alloc_size([&](){
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
1872 [ # # # # : 0 : U32x1 num_results = end->clone() - begin;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1873 [ # # # # : 0 : U32x1 num_results_cpy = num_results.clone();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1874 [ # # # # : 0 : U32x1 batch_size = M.batch_size == 0 ? num_results.clone() : U32x1(M.batch_size);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
1875 [ # # # # : 0 : U32x1 batch_size_cpy = batch_size.clone();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1876 [ # # # # : 0 : return Select(batch_size < num_results, batch_size_cpy, num_results_cpy);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
1877 : 0 : }());
1878 [ # # # # : 0 : Ptr<U32x1> buffer_address = Module::Allocator().malloc<uint32_t>(alloc_size);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
1879 : :
1880 : : /*----- Emit setup code *after* allocating memory to guarantee sequential memory allocation for pipeline. -----*/
1881 [ # # # # : 0 : setup();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1882 : :
1883 : : /*----- Emit loop code. -----*/
1884 [ # # # # : 0 : Var<U32x1> num_tuples_in_batch;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1885 [ # # # # : 0 : Var<Ptr<U32x1>> ptr;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1886 [ # # # # : 0 : WHILE (begin < end->clone()) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
1887 [ # # # # : 0 : auto end_cpy = end->clone();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1888 [ # # # # : 0 : num_tuples_in_batch = Select(*end - begin > alloc_size, alloc_size, end_cpy - begin);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
1889 : : /* Call host to fill buffer memory with next batch of tuple ids. */
1890 [ # # # # : 0 : Module::Get().emit_call<void>(
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
1891 : 0 : /* fn= */ scan_fn,
1892 [ # # # # : 0 : /* index_id= */ index_id,
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1893 [ # # # # : 0 : /* entry_offset= */ begin.val(),
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1894 [ # # # # : 0 : /* address= */ buffer_address.clone(),
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1895 [ # # # # : 0 : /* batch_size= */ num_tuples_in_batch.val()
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1896 : : );
1897 [ # # # # : 0 : begin += num_tuples_in_batch;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1898 [ # # # # : 0 : ptr = buffer_address.clone();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
1899 [ # # # # : 0 : WHILE(num_tuples_in_batch > 0U) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
1900 [ # # # # : 0 : static Schema empty_schema;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
1901 [ # # # # : 0 : compile_load_point_access(
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1902 : 0 : /* tuple_value_schema= */ M.scan.schema(),
1903 : : /* tuple_address_schema= */ empty_schema,
1904 [ # # # # : 0 : /* base_address= */ get_base_address(M.scan.store().table().name()),
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
1905 [ # # # # : 0 : /* layout= */ M.scan.store().table().layout(),
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1906 [ # # # # : 0 : /* layout_schema= */ M.scan.store().table().schema(M.scan.alias()),
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
1907 [ # # # # : 0 : /* tuple_id= */ *ptr
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
1908 : : );
1909 [ # # # # : 0 : pipeline();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1910 [ # # # # : 0 : num_tuples_in_batch -= 1U;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1911 [ # # # # : 0 : ptr += 1;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1912 : : }
1913 : 0 : }
1914 : :
1915 : : /*----- Emit teardown code. -----*/
1916 [ # # # # : 0 : teardown();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1917 : :
1918 : : /*----- Free buffer memory. -----*/
1919 [ # # # # : 0 : IF (alloc_size > U32x1(0)) { // only free if actually allocated
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
1920 : 0 : Module::Allocator().free(buffer_address, alloc_size);
1921 : 0 : };
1922 [ # # # # : 0 : } else if (options::index_scan_compilation_strategy == option_configs::IndexScanCompilationStrategy::EXPOSED_MEMORY) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1923 [ # # # # : 0 : M_unreachable("not implemented yet");
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1924 : : } else {
1925 [ # # # # : 0 : M_unreachable("unknwon compilation strategy");
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1926 : : }
1927 : 0 : }
1928 : :
1929 : : /** Resolves the index scan strategy and calls the appropriate codegen function. */
1930 : : template<idx::IndexMethod IndexMethod, typename Index, sql_type SqlT>
1931 : 0 : void index_scan_resolve_strategy(const Index &index, const index_scan_bounds_t &bounds, const Match<IndexScan<IndexMethod>> &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
1932 : : {
1933 [ # # # # : 0 : if (options::index_scan_strategy == option_configs::IndexScanStrategy::COMPILATION) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1934 [ # # # # : 0 : index_scan_codegen_compilation<IndexMethod, Index, SqlT>(index, bounds, M, std::move(setup), std::move(pipeline), std::move(teardown));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1935 [ # # # # : 0 : } else if (options::index_scan_strategy == option_configs::IndexScanStrategy::INTERPRETATION) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1936 [ # # # # : 0 : index_scan_codegen_interpretation<IndexMethod, Index>(index, bounds, M, std::move(setup), std::move(pipeline), std::move(teardown));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1937 [ # # # # : 0 : } else if (options::index_scan_strategy == option_configs::IndexScanStrategy::HYBRID) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1938 [ # # # # : 0 : index_scan_codegen_hybrid<IndexMethod, Index, SqlT>(index, bounds, M, std::move(setup), std::move(pipeline), std::move(teardown));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
1939 : 0 : } else {
1940 : 0 : M_unreachable("invalid index access strategy");
1941 : : }
1942 : 0 : }
1943 : :
1944 : : /** Resolves the index method and calls the appropriate codegen function. */
1945 : : template<idx::IndexMethod IndexMethod, typename AttrT, sql_type SqlT>
1946 : 0 : void index_scan_resolve_index_method(const index_scan_bounds_t &bounds, const Match<IndexScan<IndexMethod>> &M,
1947 : : setup_t setup, pipeline_t pipeline, teardown_t teardown)
1948 : : {
1949 : : /*----- Lookup index. -----*/
1950 : 0 : Catalog &C = Catalog::Get();
1951 : 0 : auto &DB = C.get_database_in_use();
1952 : 0 : auto &table_name = M.scan.store().table().name();
1953 : 0 : auto &attribute_name = bounds.attribute.id.name;
1954 : 0 : auto &index_base = DB.get_index(table_name, attribute_name, IndexMethod);
1955 : :
1956 : : /*----- Resolve index type. -----*/
1957 : : if constexpr(IndexMethod == idx::IndexMethod::Array and requires { typename idx::ArrayIndex<AttrT>; }) {
1958 : 0 : auto &index = as<const idx::ArrayIndex<AttrT>>(index_base);
1959 [ # # # # : 0 : index_scan_resolve_strategy<IndexMethod, const idx::ArrayIndex<AttrT>, SqlT>(
# # # # #
# # # # #
# # ]
1960 : 0 : index, bounds, M, std::move(setup), std::move(pipeline), std::move(teardown)
1961 : : );
1962 : : } else if constexpr(IndexMethod == idx::IndexMethod::Rmi and requires { typename idx::RecursiveModelIndex<AttrT>; }) {
1963 : 0 : auto &index = as<const idx::RecursiveModelIndex<AttrT>>(index_base);
1964 [ # # # # : 0 : index_scan_resolve_strategy<IndexMethod, const idx::RecursiveModelIndex<AttrT>, SqlT>(
# # # # #
# # # ]
1965 : 0 : index, bounds, M, std::move(setup), std::move(pipeline), std::move(teardown)
1966 : : );
1967 : : } else {
1968 : 0 : M_unreachable("invalid index method");
1969 : : }
1970 : 0 : }
1971 : :
1972 : : /** Resolves the attribute type and calls the appropriate codegen function. */
1973 : : template<idx::IndexMethod IndexMethod>
1974 : 0 : void index_scan_resolve_attribute_type(const Match<IndexScan<IndexMethod>> &M,
1975 : : setup_t setup, pipeline_t pipeline, teardown_t teardown)
1976 : : {
1977 : : /*----- Extract bounds from CNF. -----*/
1978 : 0 : auto &cnf = M.filter.filter();
1979 : 0 : auto bounds = extract_index_scan_bounds(cnf);
1980 : :
1981 : : /*----- Resolve attribute type. -----*/
1982 : : #define RESOLVE_INDEX_METHOD(ATTRTYPE, SQLTYPE) \
1983 : : index_scan_resolve_index_method<IndexMethod, ATTRTYPE, SQLTYPE>(bounds, M, std::move(setup), std::move(pipeline), std::move(teardown))
1984 : :
1985 [ # # # # : 0 : visit(overloaded {
# # # # #
# # # # #
# # # # #
# ]
1986 [ # # # # ]: 0 : [&](const Boolean&) { RESOLVE_INDEX_METHOD(bool, _Boolx1); },
1987 : 0 : [&](const Numeric &n) {
1988 [ # # # # : 0 : switch (n.kind) {
# # ]
1989 : : case Numeric::N_Int:
1990 : : case Numeric::N_Decimal:
1991 [ # # # # : 0 : switch (n.size()) {
# # # # #
# ]
1992 : 0 : default: M_unreachable("invalid size");
1993 [ # # # # ]: 0 : case 8: RESOLVE_INDEX_METHOD(int8_t, _I8x1); break;
1994 [ # # # # ]: 0 : case 16: RESOLVE_INDEX_METHOD(int16_t, _I16x1); break;
1995 [ # # # # ]: 0 : case 32: RESOLVE_INDEX_METHOD(int32_t, _I32x1); break;
1996 [ # # # # ]: 0 : case 64: RESOLVE_INDEX_METHOD(int64_t, _I64x1); break;
1997 : : }
1998 : 0 : break;
1999 : : case Numeric::N_Float:
2000 [ # # # # : 0 : switch (n.size()) {
# # ]
2001 : 0 : default: M_unreachable("invalid size");
2002 [ # # # # ]: 0 : case 32: RESOLVE_INDEX_METHOD(float, _Floatx1); break;
2003 [ # # # # ]: 0 : case 64: RESOLVE_INDEX_METHOD(double, _Doublex1); break;
2004 : : }
2005 : 0 : break;
2006 : : }
2007 : 0 : },
2008 [ # # # # ]: 0 : [&](const CharacterSequence&) { RESOLVE_INDEX_METHOD(const char*, NChar); },
2009 [ # # # # ]: 0 : [&](const Date&) { RESOLVE_INDEX_METHOD(int32_t, _I32x1); },
2010 [ # # # # ]: 0 : [&](const DateTime&) { RESOLVE_INDEX_METHOD(int64_t, _I64x1); },
2011 : 0 : [](auto&&) { M_unreachable("invalid type"); },
2012 : 0 : }, *bounds.attribute.type);
2013 : :
2014 : : #undef RESOLVE_INDEX_METHOD
2015 : 0 : }
2016 : :
2017 : : template<idx::IndexMethod IndexMethod>
2018 : 0 : void IndexScan<IndexMethod>::execute(const Match<IndexScan<IndexMethod>> &M,
2019 : : setup_t setup, pipeline_t pipeline, teardown_t teardown)
2020 : : {
2021 : 0 : auto &schema = M.scan.schema();
2022 [ # # # # : 0 : M_insist(schema == schema.drop_constants().deduplicate(), "Schema of `ScanOperator` must neither contain NULL nor duplicates");
# # # # #
# # # ]
2023 : :
2024 : 0 : auto &table = M.scan.store().table();
2025 : 0 : M_insist(not table.layout().is_finite(), "layout for `wasm::IndexScan` must be infinite");
2026 : :
2027 : : /** Generating code for an index scan requires resolving the following:
2028 : : * 1. attribute type of the indexed attribute,
2029 : : * 2. index method of the index used by the operator,
2030 : : * 3. strategy used for performing the index scan.
2031 : : * We avoid code duplication by handling each step in a separate function and forwarding the result via template
2032 : : * arguments to the next function. */
2033 [ # # # # ]: 0 : index_scan_resolve_attribute_type(M, std::move(setup), std::move(pipeline), std::move(teardown));
2034 : 0 : }
2035 : :
2036 : :
2037 : : /*======================================================================================================================
2038 : : * Filter
2039 : : *====================================================================================================================*/
2040 : :
2041 : : template<bool Predicated>
2042 : 0 : ConditionSet Filter<Predicated>::pre_condition(std::size_t child_idx, const std::tuple<const FilterOperator*>&)
2043 : : {
2044 : 0 : M_insist(child_idx == 0);
2045 : :
2046 : 0 : ConditionSet pre_cond;
2047 : :
2048 : : if constexpr (not Predicated) {
2049 : : /*----- Branching filter does not support SIMD. -----*/
2050 [ # # # # ]: 0 : pre_cond.add_condition(NoSIMD());
2051 : : }
2052 : :
2053 : 0 : return pre_cond;
2054 [ # # # # ]: 0 : }
2055 : :
2056 : : template<bool Predicated>
2057 : 0 : ConditionSet Filter<Predicated>::adapt_post_condition(const Match<Filter>&, const ConditionSet &post_cond_child)
2058 : : {
2059 : 0 : ConditionSet post_cond(post_cond_child);
2060 : :
2061 : : if constexpr (Predicated) {
2062 : : /*----- Predicated filter introduces predication. -----*/
2063 [ # # # # ]: 0 : post_cond.add_or_replace_condition(m::Predicated(true));
2064 : : }
2065 : :
2066 : 0 : return post_cond;
2067 [ # # # # ]: 0 : }
2068 : :
2069 : : template<bool Predicated>
2070 : 0 : double Filter<Predicated>::cost(const Match<Filter> &M)
2071 : : {
2072 : 0 : const cnf::CNF &cond = M.filter.filter();
2073 : 0 : const unsigned cost = std::accumulate(cond.cbegin(), cond.cend(), 0U, [](unsigned cost, const cnf::Clause &clause) {
2074 : 0 : return cost + clause.size();
2075 : : });
2076 : 0 : return cost;
2077 : : }
2078 : :
2079 : : template<bool Predicated>
2080 : 0 : void Filter<Predicated>::execute(const Match<Filter> &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
2081 : : {
2082 : : /*----- Set minimal number of SIMD lanes preferred to get fully utilized SIMD vectors for the filter condition. --*/
2083 : 0 : CodeGenContext::Get().update_num_simd_lanes_preferred(16); // set own preference
2084 : :
2085 : : /*----- Execute filter. -----*/
2086 [ # # # # ]: 0 : M.child->execute(
2087 : 0 : /* setup= */ std::move(setup),
2088 [ # # # # ]: 0 : /* pipeline= */ [&, pipeline=std::move(pipeline)](){
2089 : : if constexpr (Predicated) {
2090 : 0 : CodeGenContext::Get().env().add_predicate(M.filter.filter());
2091 : 0 : pipeline();
2092 : : } else {
2093 : 0 : M_insist(CodeGenContext::Get().num_simd_lanes() == 1, "invalid number of SIMD lanes");
2094 [ # # # # ]: 0 : IF (CodeGenContext::Get().env().compile<_Boolx1>(M.filter.filter()).is_true_and_not_null()) {
2095 : 0 : pipeline();
2096 : 0 : };
2097 : : }
2098 : 0 : },
2099 : 0 : /* teardown= */ std::move(teardown)
2100 : : );
2101 : 0 : }
2102 : :
2103 : :
2104 : : /*======================================================================================================================
2105 : : * LazyDisjunctiveFilter
2106 : : *====================================================================================================================*/
2107 : :
2108 : 0 : ConditionSet LazyDisjunctiveFilter::pre_condition(std::size_t child_idx, const std::tuple<const FilterOperator*>&)
2109 : : {
2110 : 0 : M_insist(child_idx == 0);
2111 : :
2112 : 0 : ConditionSet pre_cond;
2113 : :
2114 : : /*----- Lazy disjunctive filter does not support SIMD. -----*/
2115 [ # # # # ]: 0 : pre_cond.add_condition(NoSIMD());
2116 : :
2117 : 0 : return pre_cond;
2118 [ # # ]: 0 : }
2119 : :
2120 : 0 : double LazyDisjunctiveFilter::cost(const Match<LazyDisjunctiveFilter> &M)
2121 : : {
2122 : 0 : const cnf::CNF &cond = M.filter.filter();
2123 : 0 : M_insist(cond.size() == 1, "disjunctive filter condition must be a single clause");
2124 : 0 : return cond[0].size() / 2.0; // on avg. half the number of predicates in the clause XXX consider selectivities
2125 : : }
2126 : :
2127 : 0 : void LazyDisjunctiveFilter::execute(const Match<LazyDisjunctiveFilter> &M, setup_t setup, pipeline_t pipeline,
2128 : : teardown_t teardown)
2129 : : {
2130 : 0 : const cnf::Clause &clause = M.filter.filter()[0];
2131 : :
2132 [ # # ]: 0 : M.child->execute(
2133 : 0 : /* setup= */ std::move(setup),
2134 [ # # ]: 0 : /* pipeline= */ [&, pipeline=std::move(pipeline)](){
2135 : 0 : M_insist(CodeGenContext::Get().num_simd_lanes() == 1, "invalid number of SIMD lanes");
2136 [ # # ]: 0 : BLOCK(lazy_disjunctive_filter)
2137 : : {
2138 [ # # # # ]: 0 : BLOCK(lazy_disjunctive_filter_then)
2139 : : {
2140 [ # # ]: 0 : for (const cnf::Predicate &pred : clause) {
2141 [ # # # # : 0 : auto cond = CodeGenContext::Get().env().compile<_Boolx1>(*pred);
# # # # ]
2142 [ # # ]: 0 : if (pred.negative())
2143 [ # # # # ]: 0 : GOTO(cond.is_false_and_not_null(), lazy_disjunctive_filter_then); // break to remainder of pipline
2144 : : else
2145 [ # # # # ]: 0 : GOTO(cond.is_true_and_not_null(), lazy_disjunctive_filter_then); // break to remainder of pipline
2146 : 0 : }
2147 [ # # ]: 0 : GOTO(lazy_disjunctive_filter); // skip pipeline
2148 : : }
2149 [ # # ]: 0 : pipeline();
2150 : : }
2151 : 0 : },
2152 : 0 : /* teardown= */ std::move(teardown)
2153 : : );
2154 : 0 : }
2155 : :
2156 : :
2157 : : /*======================================================================================================================
2158 : : * Projection
2159 : : *====================================================================================================================*/
2160 : :
2161 : 0 : ConditionSet Projection::pre_condition(
2162 : : std::size_t child_idx,
2163 : : const std::tuple<const ProjectionOperator*> &partial_inner_nodes)
2164 : : {
2165 : 0 : M_insist(child_idx == 0);
2166 : :
2167 : 0 : ConditionSet pre_cond;
2168 : :
2169 : 0 : auto &projection = *std::get<0>(partial_inner_nodes);
2170 : :
2171 [ # # # # ]: 0 : if (not projection.children().empty()) { // projections starting a pipeline produce only a single tuple, i.e. no SIMD
2172 : : /*----- Projection does only support SIMD if all expressions can be computed using SIMD instructions. -----*/
2173 : 0 : auto is_simd_computable = [](const ast::Expr &e){
2174 : 0 : bool simd_computable = true;
2175 : 0 : visit(overloaded {
2176 : 0 : [&](const ast::BinaryExpr &b) -> void {
2177 [ # # ]: 0 : if (b.lhs->type()->is_character_sequence() or b.rhs->type()->is_character_sequence()) {
2178 : 0 : simd_computable = false; // string operations are not SIMDfiable
2179 : 0 : throw visit_stop_recursion(); // abort recursion
2180 : : }
2181 [ # # # # : 0 : if (b.common_operand_type->is_integral() and b.op().type == TK_SLASH) {
# # ]
2182 : 0 : simd_computable = false; // integer division is not SIMDfiable
2183 : 0 : throw visit_stop_recursion(); // abort recursion
2184 : : }
2185 [ # # ]: 0 : if (b.op().type == TK_PERCENT) {
2186 : 0 : simd_computable = false; // modulo is not SIMDfiable
2187 : 0 : throw visit_stop_recursion(); // abort recursion
2188 : : }
2189 : 0 : },
2190 : 0 : [](auto&) -> void {
2191 : : /* designators, constants, unary expressions, NULL(), INT(), already computed aggregates and results
2192 : : * of a nested query are SIMDfiable; nothing to be done */
2193 : 0 : },
2194 : 0 : }, e, m::tag<m::ast::ConstPreOrderExprVisitor>());
2195 : 0 : return simd_computable;
2196 : : };
2197 : 0 : auto pred = [&](auto &p){ return not is_simd_computable(p.first); };
2198 [ # # # # : 0 : if (std::any_of(projection.projections().cbegin(), projection.projections().cend(), pred))
# # # # ]
2199 [ # # # # ]: 0 : pre_cond.add_condition(NoSIMD());
2200 : 0 : }
2201 : :
2202 : 0 : return pre_cond;
2203 [ # # ]: 0 : }
2204 : :
2205 : 0 : ConditionSet Projection::adapt_post_condition(const Match<Projection> &M, const ConditionSet &post_cond_child)
2206 : : {
2207 : 0 : ConditionSet post_cond(post_cond_child);
2208 : :
2209 : : /*----- Project and rename in duplicated post condition. -----*/
2210 [ # # # # : 0 : M_insist(M.projection.projections().size() == M.projection.schema().num_entries(),
# # # # ]
2211 : : "projections must match the operator's schema");
2212 : 0 : std::vector<std::pair<Schema::Identifier, Schema::Identifier>> old2new;
2213 [ # # ]: 0 : auto p = M.projection.projections().begin();
2214 [ # # # # : 0 : for (auto &e: M.projection.schema()) {
# # # # ]
2215 : 0 : auto pred = [&e](const auto &p) { return p.second == e.id; };
2216 [ # # # # ]: 0 : if (std::find_if(old2new.cbegin(), old2new.cend(), pred) == old2new.cend()) {
2217 [ # # # # ]: 0 : M_insist(p != M.projection.projections().end());
2218 [ # # # # ]: 0 : old2new.emplace_back(Schema::Identifier(p->first.get()), e.id);
2219 : 0 : }
2220 : 0 : ++p;
2221 : : }
2222 [ # # ]: 0 : post_cond.project_and_rename(old2new);
2223 : :
2224 [ # # ]: 0 : if (not M.child) {
2225 : : /*----- Leaf projection does not introduce predication. -----*/
2226 [ # # # # ]: 0 : post_cond.add_condition(Predicated(false));
2227 : :
2228 : : /*----- Leaf projection does not introduce SIMD. -----*/
2229 [ # # # # ]: 0 : post_cond.add_condition(NoSIMD());
2230 : 0 : }
2231 : :
2232 : 0 : return post_cond;
2233 [ # # ]: 0 : }
2234 : :
2235 : 0 : void Projection::execute(const Match<Projection> &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
2236 : : {
2237 : 0 : auto execute_projection = [&, pipeline=std::move(pipeline)](){
2238 : 0 : auto &old_env = CodeGenContext::Get().env();
2239 : 0 : Environment new_env; // fresh environment
2240 : :
2241 : : /*----- If predication is used, move predicate to newly created environment. -----*/
2242 [ # # ]: 0 : if (old_env.predicated())
2243 [ # # # # ]: 0 : new_env.add_predicate(old_env.extract_predicate());
2244 : :
2245 : : /*----- Add projections to newly created environment. -----*/
2246 [ # # ]: 0 : M_insist(M.projection.projections().size() == M.projection.schema().num_entries(),
2247 : : "projections must match the operator's schema");
2248 : 0 : auto p = M.projection.projections().begin();
2249 [ # # ]: 0 : for (auto &e: M.projection.schema()) {
2250 [ # # # # : 0 : if (not new_env.has(e.id) and not e.id.is_constant()) { // no duplicate and no constant
# # # # ]
2251 [ # # # # ]: 0 : if (old_env.has(e.id)) {
2252 : : /*----- Migrate compiled expression to new context. ------*/
2253 [ # # # # : 0 : new_env.add(e.id, old_env.get(e.id)); // to retain `e.id` for later compilation of expressions
# # ]
2254 : 0 : } else {
2255 : : /*----- Compile expression. -----*/
2256 [ # # ]: 0 : M_insist(p != M.projection.projections().end());
2257 [ # # ]: 0 : std::visit(overloaded {
2258 : 0 : [&]<typename T, std::size_t L>(Expr<T, L> value) -> void {
2259 [ # # # # : 0 : if (value.can_be_null()) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
2260 : 0 : Var<Expr<T, L>> var(value); // introduce variable s.t. uses only load from it
2261 [ # # # # : 0 : new_env.add(e.id, var);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
2262 : 0 : } else {
2263 : : /* introduce variable w/o NULL bit s.t. uses only load from it */
2264 [ # # # # : 0 : Var<PrimitiveExpr<T, L>> var(value.insist_not_null());
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
2265 [ # # # # : 0 : new_env.add(e.id, Expr<T, L>(var));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
2266 : 0 : }
2267 : 0 : },
2268 : 0 : [&](NChar value) -> void {
2269 [ # # ]: 0 : Var<Ptr<Charx1>> var(value.val()); // introduce variable s.t. uses only load from it
2270 [ # # # # : 0 : new_env.add(e.id, NChar(var, value.can_be_null(), value.length(),
# # # # #
# # # ]
2271 [ # # ]: 0 : value.guarantees_terminating_nul()));
2272 : 0 : },
2273 : 0 : [](std::monostate) -> void { M_unreachable("invalid expression"); },
2274 [ # # ]: 0 : }, old_env.compile(p->first));
2275 : : }
2276 : 0 : }
2277 : 0 : ++p;
2278 : : }
2279 : :
2280 : : /*----- Resume pipeline with newly created environment. -----*/
2281 : : {
2282 [ # # # # : 0 : auto S = CodeGenContext::Get().scoped_environment(std::move(new_env));
# # ]
2283 [ # # ]: 0 : pipeline();
2284 : 0 : }
2285 : 0 : };
2286 : :
2287 [ # # ]: 0 : if (M.child) {
2288 : : /*----- Set minimal number of SIMD lanes preferred to get fully utilized SIMD vectors *after* the projection. */
2289 : 0 : uint64_t min_size_in_bytes = 16;
2290 [ # # # # ]: 0 : for (auto &p : M.projection.projections()) {
2291 [ # # # # ]: 0 : visit(overloaded {
2292 : 0 : [](const m::ast::ErrorExpr&) -> void { M_unreachable("no errors at this stage"); },
2293 : 0 : [](const m::ast::Designator&) -> void { /* nothing to be done */ },
2294 : 0 : [](const m::ast::Constant&) -> void { /* nothing to be done */ },
2295 : 0 : [](const m::ast::QueryExpr&) -> void { /* nothing to be done */ },
2296 : 0 : [&min_size_in_bytes](const m::ast::FnApplicationExpr &fn) -> void {
2297 [ # # ]: 0 : if (fn.get_function().is_aggregate())
2298 : 0 : throw visit_skip_subtree(); // skip arguments to already computed aggregate
2299 : 0 : min_size_in_bytes = std::min(min_size_in_bytes, (fn.type()->size() + 7) / 8);
2300 [ # # ]: 0 : if (min_size_in_bytes == 1)
2301 : 0 : throw visit_stop_recursion(); // abort recursion
2302 : 0 : },
2303 : 0 : [&min_size_in_bytes](auto &e) -> void { // i.e. for unary and binary expressions
2304 : 0 : min_size_in_bytes = std::min(min_size_in_bytes, (e.type()->size() + 7) / 8);
2305 [ # # # # ]: 0 : if (min_size_in_bytes == 1)
2306 : 0 : throw visit_stop_recursion(); // abort recursion
2307 : 0 : }
2308 : 0 : }, p.first.get(), m::tag<m::ast::ConstPreOrderExprVisitor>());
2309 : : }
2310 [ # # # # ]: 0 : CodeGenContext::Get().update_num_simd_lanes_preferred(16 / min_size_in_bytes); // set own preference
2311 : :
2312 : : /*----- Execute projection. -----*/
2313 [ # # # # ]: 0 : M.child->get()->execute(std::move(setup), std::move(execute_projection), std::move(teardown));
2314 : 0 : } else {
2315 : : /*----- Execute projection. -----*/
2316 [ # # ]: 0 : setup();
2317 [ # # # # ]: 0 : CodeGenContext::Get().set_num_simd_lanes(1); // since only a single tuple is produced
2318 [ # # ]: 0 : execute_projection();
2319 [ # # ]: 0 : teardown();
2320 : : }
2321 : 0 : }
2322 : :
2323 : :
2324 : : /*======================================================================================================================
2325 : : * Grouping
2326 : : *====================================================================================================================*/
2327 : :
2328 : 0 : ConditionSet HashBasedGrouping::pre_condition(std::size_t child_idx, const std::tuple<const GroupingOperator*>&)
2329 : : {
2330 : 0 : M_insist(child_idx == 0);
2331 : :
2332 : 0 : ConditionSet pre_cond;
2333 : :
2334 : : /*----- Hash-based grouping does not support SIMD. -----*/
2335 [ # # # # ]: 0 : pre_cond.add_condition(NoSIMD());
2336 : :
2337 : 0 : return pre_cond;
2338 [ # # ]: 0 : }
2339 : :
2340 : 0 : double HashBasedGrouping::cost(const Match<HashBasedGrouping> &M)
2341 : : {
2342 : 0 : return 1.5 * M.child->get_matched_root().info().estimated_cardinality;
2343 : : }
2344 : :
2345 : 0 : ConditionSet HashBasedGrouping::post_condition(const Match<HashBasedGrouping>&)
2346 : : {
2347 : 0 : ConditionSet post_cond;
2348 : :
2349 : : /*----- Hash-based grouping does not introduce predication (it is already handled by the hash table). -----*/
2350 [ # # # # ]: 0 : post_cond.add_condition(Predicated(false));
2351 : :
2352 : : /*----- Hash-based grouping does not introduce SIMD. -----*/
2353 [ # # # # ]: 0 : post_cond.add_condition(NoSIMD());
2354 : :
2355 : 0 : return post_cond;
2356 [ # # ]: 0 : }
2357 : :
2358 : 0 : void HashBasedGrouping::execute(const Match<HashBasedGrouping> &M, setup_t setup, pipeline_t pipeline,
2359 : : teardown_t teardown)
2360 : : {
2361 : : // TODO: determine setup
2362 : 0 : const uint64_t AGGREGATES_SIZE_THRESHOLD_IN_BITS =
2363 : 0 : M.use_in_place_values ? std::numeric_limits<uint64_t>::max() : 0;
2364 : :
2365 : 0 : const auto num_keys = M.grouping.group_by().size();
2366 : :
2367 : : /*----- Compute hash table schema and information about aggregates, especially AVG aggregates. -----*/
2368 : 0 : Schema ht_schema;
2369 : : /* Add key(s). */
2370 [ # # ]: 0 : for (std::size_t i = 0; i < num_keys; ++i) {
2371 [ # # # # ]: 0 : auto &e = M.grouping.schema()[i];
2372 [ # # # # ]: 0 : ht_schema.add(e.id, e.type, e.constraints);
2373 : 0 : }
2374 : : /* Add payload. */
2375 [ # # # # : 0 : auto p = compute_aggregate_info(M.grouping.aggregates(), M.grouping.schema(), num_keys);
# # ]
2376 : 0 : const auto &aggregates = p.first;
2377 : 0 : const auto &avg_aggregates = p.second;
2378 : 0 : uint64_t aggregates_size_in_bits = 0;
2379 [ # # ]: 0 : for (auto &info : aggregates) {
2380 [ # # # # ]: 0 : ht_schema.add(info.entry);
2381 [ # # ]: 0 : aggregates_size_in_bits += info.entry.type->size();
2382 : : }
2383 : :
2384 : : /*----- Compute initial capacity of hash table. -----*/
2385 [ # # ]: 0 : uint32_t initial_capacity = compute_initial_ht_capacity(M.grouping, M.load_factor);
2386 : :
2387 : : /*----- Create hash table. -----*/
2388 : 0 : std::unique_ptr<HashTable> ht;
2389 [ # # ]: 0 : std::vector<HashTable::index_t> key_indices(num_keys);
2390 [ # # ]: 0 : std::iota(key_indices.begin(), key_indices.end(), 0);
2391 [ # # ]: 0 : if (M.use_open_addressing_hashing) {
2392 [ # # ]: 0 : if (aggregates_size_in_bits < AGGREGATES_SIZE_THRESHOLD_IN_BITS)
2393 [ # # ]: 0 : ht = std::make_unique<GlobalOpenAddressingInPlaceHashTable>(ht_schema, std::move(key_indices),
2394 : : initial_capacity);
2395 : : else
2396 [ # # ]: 0 : ht = std::make_unique<GlobalOpenAddressingOutOfPlaceHashTable>(ht_schema, std::move(key_indices),
2397 : : initial_capacity);
2398 [ # # ]: 0 : if (M.use_quadratic_probing)
2399 [ # # # # ]: 0 : as<OpenAddressingHashTableBase>(*ht).set_probing_strategy<QuadraticProbing>();
2400 : : else
2401 [ # # # # ]: 0 : as<OpenAddressingHashTableBase>(*ht).set_probing_strategy<LinearProbing>();
2402 : 0 : } else {
2403 [ # # ]: 0 : ht = std::make_unique<GlobalChainedHashTable>(ht_schema, std::move(key_indices), initial_capacity);
2404 : : }
2405 : :
2406 : : /*----- Create child function. -----*/
2407 [ # # # # : 0 : FUNCTION(hash_based_grouping_child_pipeline, void(void)) // create function for pipeline
# # # # ]
2408 : : {
2409 [ # # # # ]: 0 : auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
2410 : :
2411 : 0 : std::optional<HashTable::entry_t> dummy; ///< *local* dummy slot
2412 : :
2413 [ # # ]: 0 : M.child->execute(
2414 [ # # # # ]: 0 : /* setup= */ setup_t::Make_Without_Parent([&](){
2415 : 0 : ht->setup();
2416 : 0 : ht->set_high_watermark(M.load_factor);
2417 : 0 : dummy.emplace(ht->dummy_entry()); // create dummy slot to ignore NULL values in aggregate computations
2418 : 0 : }),
2419 [ # # ]: 0 : /* pipeline= */ [&](){
2420 : 0 : M_insist(bool(dummy));
2421 : 0 : const auto &env = CodeGenContext::Get().env();
2422 : :
2423 : : /*----- Insert key if not yet done. -----*/
2424 : 0 : std::vector<SQL_t> key;
2425 [ # # ]: 0 : for (auto &p : M.grouping.group_by())
2426 [ # # # # ]: 0 : key.emplace_back(env.compile(p.first.get()));
2427 [ # # ]: 0 : auto [entry, inserted] = ht->try_emplace(std::move(key));
2428 : :
2429 : : /*----- Compute aggregates. -----*/
2430 [ # # ]: 0 : Block init_aggs("hash_based_grouping.init_aggs", false),
2431 [ # # ]: 0 : update_aggs("hash_based_grouping.update_aggs", false),
2432 [ # # ]: 0 : update_avg_aggs("hash_based_grouping.update_avg_aggs", false);
2433 [ # # ]: 0 : for (auto &info : aggregates) {
2434 : 0 : bool is_min = false; ///< flag to indicate whether aggregate function is MIN
2435 [ # # # # : 0 : switch (info.fnid) {
# # ]
2436 : : default:
2437 [ # # ]: 0 : M_unreachable("unsupported aggregate function");
2438 : : case m::Function::FN_MIN:
2439 : 0 : is_min = true; // set flag and delegate to MAX case
2440 : : case m::Function::FN_MAX: {
2441 [ # # ]: 0 : M_insist(info.args.size() == 1,
2442 : : "MIN and MAX aggregate functions expect exactly one argument");
2443 : 0 : const auto &arg = *info.args[0];
2444 [ # # ]: 0 : std::visit(overloaded {
2445 : 0 : [&]<sql_type _T>(HashTable::reference_t<_T> &&r) -> void
2446 : : requires (not (std::same_as<_T, _Boolx1> or std::same_as<_T, NChar>)) {
2447 : : using type = typename _T::type;
2448 : : using T = PrimitiveExpr<type>;
2449 : :
2450 : 0 : auto _arg = env.compile(arg);
2451 [ # # # # : 0 : _T _new_val = convert<_T>(_arg);
# # # # #
# # # ]
2452 : :
2453 [ # # # # : 0 : BLOCK_OPEN(init_aggs) {
# # # # #
# # # ]
2454 [ # # # # : 0 : auto [val_, is_null] = _new_val.clone().split();
# # # # #
# # # # #
# # # # #
# # # #
# ]
2455 [ # # # # : 0 : T val(val_); // due to structured binding and lambda closure
# # # # #
# # # ]
2456 [ # # # # : 0 : IF (is_null) {
# # # # #
# # # # #
# # # # #
# # # #
# ]
2457 [ # # # # : 0 : auto neutral = is_min ? T(std::numeric_limits<type>::max())
# # # # #
# # # ]
2458 : 0 : : T(std::numeric_limits<type>::lowest());
2459 [ # # # # : 0 : r.clone().set_value(neutral); // initialize with neutral element +inf or -inf
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
2460 [ # # # # : 0 : if (info.entry.nullable())
# # # # #
# # # #
# ]
2461 [ # # # # : 0 : r.clone().set_null(); // first value is NULL
# # # # #
# # # # #
# # # # #
# # # #
# ]
2462 [ # # # # : 0 : } ELSE {
# # # # #
# # # ]
2463 [ # # # # : 0 : r.clone().set_value(val); // initialize with first value
# # # # #
# # # # #
# # # # #
# # # #
# ]
2464 [ # # # # : 0 : if (info.entry.nullable())
# # # # #
# # # ]
2465 [ # # # # : 0 : r.clone().set_not_null(); // first value is not NULL
# # # # #
# # # ]
2466 : 0 : };
2467 : 0 : }
2468 [ # # # # : 0 : BLOCK_OPEN(update_aggs) {
# # # # #
# # # ]
2469 [ # # # # : 0 : if (_new_val.can_be_null()) {
# # # # #
# # # ]
2470 : : M_insist_no_ternary_logic();
2471 [ # # # # : 0 : auto [new_val_, new_val_is_null_] = _new_val.split();
# # # # #
# # # ]
2472 [ # # # # : 0 : auto [old_min_max_, old_min_max_is_null] = _T(r.clone()).split();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
2473 [ # # # # : 0 : const Var<Boolx1> new_val_is_null(new_val_is_null_); // due to multiple uses
# # # # #
# # # ]
2474 : :
2475 [ # # # # : 0 : auto chosen_r = Select(new_val_is_null, dummy->extract<_T>(info.entry.id),
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
2476 [ # # # # : 0 : r.clone());
# # # # #
# # # ]
2477 : : if constexpr (std::floating_point<type>) {
2478 [ # # # # : 0 : chosen_r.set_value(
# # # # #
# # # # #
# # # # #
# ]
2479 [ # # # # : 0 : is_min ? min(old_min_max_, new_val_) // update old min with new value
# # # # ]
2480 [ # # # # ]: 0 : : max(old_min_max_, new_val_) // update old max with new value
2481 : : ); // if new value is NULL, only dummy is written
2482 : : } else {
2483 [ # # # # : 0 : const Var<T> new_val(new_val_),
# # # # ]
2484 [ # # # # : 0 : old_min_max(old_min_max_); // due to multiple uses
# # # # ]
2485 [ # # # # : 0 : auto cmp = is_min ? new_val < old_min_max : new_val > old_min_max;
# # # # #
# # # # #
# # # # #
# # # #
# ]
2486 : : #if 1
2487 [ # # # # : 0 : chosen_r.set_value(
# # # # ]
2488 [ # # # # : 0 : Select(cmp,
# # # # ]
2489 : : new_val, // update to new value
2490 : : old_min_max) // do not update
2491 : : ); // if new value is NULL, only dummy is written
2492 : : #else
2493 : : IF (cmp) {
2494 : : r.set_value(new_val);
2495 : : };
2496 : : #endif
2497 : 0 : }
2498 [ # # # # : 0 : r.set_null_bit(
# # # # #
# # # ]
2499 [ # # # # : 0 : old_min_max_is_null and new_val_is_null // MIN/MAX is NULL iff all values are NULL
# # # # #
# # # ]
2500 : : );
2501 : 0 : } else {
2502 [ # # # # : 0 : auto new_val_ = _new_val.insist_not_null();
# # # # #
# # # ]
2503 [ # # # # : 0 : auto old_min_max_ = _T(r.clone()).insist_not_null();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
2504 : : if constexpr (std::floating_point<type>) {
2505 [ # # # # : 0 : r.set_value(
# # # # #
# # # # #
# # # # #
# ]
2506 [ # # # # : 0 : is_min ? min(old_min_max_, new_val_) // update old min with new value
# # # # ]
2507 [ # # # # ]: 0 : : max(old_min_max_, new_val_) // update old max with new value
2508 : : );
2509 : : } else {
2510 [ # # # # : 0 : const Var<T> new_val(new_val_),
# # # # ]
2511 [ # # # # : 0 : old_min_max(old_min_max_); // due to multiple uses
# # # # ]
2512 [ # # # # : 0 : auto cmp = is_min ? new_val < old_min_max : new_val > old_min_max;
# # # # #
# # # # #
# # # # #
# # # #
# ]
2513 : : #if 1
2514 [ # # # # : 0 : r.set_value(
# # # # ]
2515 [ # # # # : 0 : Select(cmp,
# # # # ]
2516 : : new_val, // update to new value
2517 : : old_min_max) // do not update
2518 : : );
2519 : : #else
2520 : : IF (cmp) {
2521 : : r.set_value(new_val);
2522 : : };
2523 : : #endif
2524 : 0 : }
2525 : : /* do not update NULL bit since it is already set to `false` */
2526 : 0 : }
2527 : : }
2528 : 0 : },
2529 : 0 : []<sql_type _T>(HashTable::reference_t<_T>&&) -> void
2530 : : requires std::same_as<_T,_Boolx1> or std::same_as<_T, NChar> {
2531 : 0 : M_unreachable("invalid type");
2532 : : },
2533 : 0 : [](std::monostate) -> void { M_unreachable("invalid reference"); },
2534 [ # # ]: 0 : }, entry.extract(info.entry.id));
2535 : 0 : break;
2536 : : }
2537 : : case m::Function::FN_AVG: {
2538 [ # # ]: 0 : auto it = avg_aggregates.find(info.entry.id);
2539 [ # # ]: 0 : M_insist(it != avg_aggregates.end());
2540 : 0 : const auto &avg_info = it->second;
2541 [ # # ]: 0 : M_insist(avg_info.compute_running_avg,
2542 : : "AVG aggregate may only occur for running average computations");
2543 [ # # ]: 0 : M_insist(info.args.size() == 1, "AVG aggregate function expects exactly one argument");
2544 : 0 : const auto &arg = *info.args[0];
2545 : :
2546 [ # # ]: 0 : auto r = entry.extract<_Doublex1>(info.entry.id);
2547 [ # # ]: 0 : auto _arg = env.compile(arg);
2548 [ # # ]: 0 : _Doublex1 _new_val = convert<_Doublex1>(_arg);
2549 : :
2550 [ # # ]: 0 : BLOCK_OPEN(init_aggs) {
2551 [ # # # # ]: 0 : auto [val_, is_null] = _new_val.clone().split();
2552 [ # # ]: 0 : Doublex1 val(val_); // due to structured binding and lambda closure
2553 [ # # ]: 0 : IF (is_null) {
2554 [ # # # # ]: 0 : r.clone().set_value(Doublex1(0.0)); // initialize with neutral element 0
2555 [ # # ]: 0 : if (info.entry.nullable())
2556 [ # # ]: 0 : r.clone().set_null(); // first value is NULL
2557 [ # # ]: 0 : } ELSE {
2558 [ # # # # ]: 0 : r.clone().set_value(val); // initialize with first value
2559 [ # # ]: 0 : if (info.entry.nullable())
2560 [ # # ]: 0 : r.clone().set_not_null(); // first value is not NULL
2561 : 0 : };
2562 : 0 : }
2563 [ # # ]: 0 : BLOCK_OPEN(update_avg_aggs) {
2564 : : /* Compute AVG as iterative mean as described in Knuth, The Art of Computer Programming
2565 : : * Vol 2, section 4.2.2. */
2566 [ # # ]: 0 : if (_new_val.can_be_null()) {
2567 : : M_insist_no_ternary_logic();
2568 [ # # ]: 0 : auto [new_val, new_val_is_null_] = _new_val.split();
2569 [ # # # # : 0 : auto [old_avg_, old_avg_is_null] = _Doublex1(r.clone()).split();
# # ]
2570 [ # # ]: 0 : const Var<Boolx1> new_val_is_null(new_val_is_null_); // due to multiple uses
2571 [ # # ]: 0 : const Var<Doublex1> old_avg(old_avg_); // due to multiple uses
2572 : :
2573 [ # # ]: 0 : auto delta_absolute = new_val - old_avg;
2574 [ # # # # : 0 : auto running_count = _I64x1(entry.get<_I64x1>(avg_info.running_count)).insist_not_null();
# # ]
2575 [ # # # # ]: 0 : auto delta_relative = delta_absolute / running_count.to<double>();
2576 : :
2577 [ # # # # : 0 : auto chosen_r = Select(new_val_is_null, dummy->extract<_Doublex1>(info.entry.id),
# # ]
2578 [ # # ]: 0 : r.clone());
2579 [ # # ]: 0 : chosen_r.set_value(
2580 [ # # ]: 0 : old_avg + delta_relative // update old average with new value
2581 : : ); // if new value is NULL, only dummy is written
2582 [ # # ]: 0 : r.set_null_bit(
2583 [ # # ]: 0 : old_avg_is_null and new_val_is_null // AVG is NULL iff all values are NULL
2584 : : );
2585 : 0 : } else {
2586 [ # # ]: 0 : auto new_val = _new_val.insist_not_null();
2587 [ # # # # : 0 : auto old_avg_ = _Doublex1(r.clone()).insist_not_null();
# # ]
2588 [ # # ]: 0 : const Var<Doublex1> old_avg(old_avg_); // due to multiple uses
2589 : :
2590 [ # # ]: 0 : auto delta_absolute = new_val - old_avg;
2591 [ # # # # : 0 : auto running_count = _I64x1(entry.get<_I64x1>(avg_info.running_count)).insist_not_null();
# # ]
2592 [ # # # # ]: 0 : auto delta_relative = delta_absolute / running_count.to<double>();
2593 [ # # ]: 0 : r.set_value(
2594 [ # # ]: 0 : old_avg + delta_relative // update old average with new value
2595 : : );
2596 : : /* do not update NULL bit since it is already set to `false` */
2597 : 0 : }
2598 : : }
2599 : : break;
2600 : 0 : }
2601 : : case m::Function::FN_SUM: {
2602 [ # # ]: 0 : M_insist(info.args.size() == 1, "SUM aggregate function expects exactly one argument");
2603 : 0 : const auto &arg = *info.args[0];
2604 [ # # ]: 0 : std::visit(overloaded {
2605 : 0 : [&]<sql_type _T>(HashTable::reference_t<_T> &&r) -> void
2606 : : requires (not (std::same_as<_T, _Boolx1> or std::same_as<_T, NChar>)) {
2607 : : using type = typename _T::type;
2608 : : using T = PrimitiveExpr<type>;
2609 : :
2610 : 0 : auto _arg = env.compile(arg);
2611 [ # # # # : 0 : _T _new_val = convert<_T>(_arg);
# # # # #
# # # ]
2612 : :
2613 [ # # # # : 0 : BLOCK_OPEN(init_aggs) {
# # # # #
# # # ]
2614 [ # # # # : 0 : auto [val_, is_null] = _new_val.clone().split();
# # # # #
# # # # #
# # # # #
# # # #
# ]
2615 [ # # # # : 0 : T val(val_); // due to structured binding and lambda closure
# # # # #
# # # ]
2616 [ # # # # : 0 : IF (is_null) {
# # # # #
# # # ]
2617 [ # # # # : 0 : r.clone().set_value(T(type(0))); // initialize with neutral element 0
# # # # #
# # # # #
# # # # #
# # # #
# ]
2618 [ # # # # : 0 : if (info.entry.nullable())
# # # # #
# # # ]
2619 [ # # # # : 0 : r.clone().set_null(); // first value is NULL
# # # # #
# # # ]
2620 [ # # # # : 0 : } ELSE {
# # # # #
# # # ]
2621 [ # # # # : 0 : r.clone().set_value(val); // initialize with first value
# # # # #
# # # # #
# # # # #
# # # #
# ]
2622 [ # # # # : 0 : if (info.entry.nullable())
# # # # #
# # # ]
2623 [ # # # # : 0 : r.clone().set_not_null(); // first value is not NULL
# # # # #
# # # ]
2624 : 0 : };
2625 : 0 : }
2626 [ # # # # : 0 : BLOCK_OPEN(update_aggs) {
# # # # #
# # # ]
2627 [ # # # # : 0 : if (_new_val.can_be_null()) {
# # # # #
# # # ]
2628 : : M_insist_no_ternary_logic();
2629 [ # # # # : 0 : auto [new_val, new_val_is_null_] = _new_val.split();
# # # # #
# # # ]
2630 [ # # # # : 0 : auto [old_sum, old_sum_is_null] = _T(r.clone()).split();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
2631 [ # # # # : 0 : const Var<Boolx1> new_val_is_null(new_val_is_null_); // due to multiple uses
# # # # #
# # # ]
2632 : :
2633 [ # # # # : 0 : auto chosen_r = Select(new_val_is_null, dummy->extract<_T>(info.entry.id),
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
2634 [ # # # # : 0 : r.clone());
# # # # #
# # # ]
2635 [ # # # # : 0 : chosen_r.set_value(
# # # # #
# # # ]
2636 [ # # # # : 0 : old_sum + new_val // add new value to old sum
# # # # #
# # # # #
# # # # #
# # # #
# ]
2637 : : ); // if new value is NULL, only dummy is written
2638 [ # # # # : 0 : r.set_null_bit(
# # # # #
# # # ]
2639 [ # # # # : 0 : old_sum_is_null and new_val_is_null // SUM is NULL iff all values are NULL
# # # # #
# # # ]
2640 : : );
2641 : 0 : } else {
2642 [ # # # # : 0 : auto new_val = _new_val.insist_not_null();
# # # # #
# # # ]
2643 [ # # # # : 0 : auto old_sum = _T(r.clone()).insist_not_null();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
2644 [ # # # # : 0 : r.set_value(
# # # # #
# # # ]
2645 [ # # # # : 0 : old_sum + new_val // add new value to old sum
# # # # #
# # # # #
# # # # #
# # # #
# ]
2646 : : );
2647 : : /* do not update NULL bit since it is already set to `false` */
2648 : 0 : }
2649 : : }
2650 : 0 : },
2651 : 0 : []<sql_type _T>(HashTable::reference_t<_T>&&) -> void
2652 : : requires std::same_as<_T,_Boolx1> or std::same_as<_T, NChar> {
2653 : 0 : M_unreachable("invalid type");
2654 : : },
2655 : 0 : [](std::monostate) -> void { M_unreachable("invalid reference"); },
2656 [ # # ]: 0 : }, entry.extract(info.entry.id));
2657 : 0 : break;
2658 : : }
2659 : : case m::Function::FN_COUNT: {
2660 [ # # ]: 0 : M_insist(info.args.size() <= 1, "COUNT aggregate function expects at most one argument");
2661 : :
2662 [ # # ]: 0 : auto r = entry.get<_I64x1>(info.entry.id); // do not extract to be able to access for AVG case
2663 : :
2664 [ # # ]: 0 : if (info.args.empty()) {
2665 [ # # ]: 0 : BLOCK_OPEN(init_aggs) {
2666 [ # # # # : 0 : r.clone() = _I64x1(1); // initialize with 1 (for first value)
# # ]
2667 : : }
2668 [ # # ]: 0 : BLOCK_OPEN(update_aggs) {
2669 [ # # # # : 0 : auto old_count = _I64x1(r.clone()).insist_not_null();
# # ]
2670 [ # # ]: 0 : r.set_value(
2671 [ # # ]: 0 : old_count + int64_t(1) // increment old count by 1
2672 : : );
2673 : : /* do not update NULL bit since it is already set to `false` */
2674 : 0 : }
2675 : 0 : } else {
2676 : 0 : const auto &arg = *info.args[0];
2677 : :
2678 [ # # ]: 0 : auto _arg = env.compile(arg);
2679 [ # # # # ]: 0 : I64x1 new_val_not_null = not_null(_arg).to<int64_t>();
2680 : :
2681 [ # # ]: 0 : BLOCK_OPEN(init_aggs) {
2682 [ # # # # : 0 : r.clone() = _I64x1(new_val_not_null.clone()); // initialize with 1 iff first value is present
# # # # ]
2683 : : }
2684 [ # # ]: 0 : BLOCK_OPEN(update_aggs) {
2685 [ # # # # : 0 : auto old_count = _I64x1(r.clone()).insist_not_null();
# # ]
2686 [ # # ]: 0 : r.set_value(
2687 [ # # # # ]: 0 : old_count + new_val_not_null // increment old count by 1 iff new value is present
2688 : : );
2689 : : /* do not update NULL bit since it is already set to `false` */
2690 : 0 : }
2691 : 0 : }
2692 : : break;
2693 : 0 : }
2694 : : }
2695 : : }
2696 : :
2697 : : /*----- If group has been inserted, initialize aggregates. Otherwise, update them. -----*/
2698 [ # # ]: 0 : IF (inserted) {
2699 : 0 : init_aggs.attach_to_current();
2700 : 0 : } ELSE {
2701 : 0 : update_aggs.attach_to_current();
2702 : 0 : update_avg_aggs.attach_to_current(); // after others to ensure that running count is incremented before
2703 : 0 : };
2704 : 0 : },
2705 [ # # ]: 0 : /* teardown= */ teardown_t::Make_Without_Parent([&](){ ht->teardown(); })
2706 : : );
2707 : 0 : }
2708 [ # # ]: 0 : hash_based_grouping_child_pipeline(); // call child function
2709 : :
2710 [ # # # # ]: 0 : auto &env = CodeGenContext::Get().env();
2711 : :
2712 : : /*----- Process each computed group. -----*/
2713 [ # # # # ]: 0 : setup_t(std::move(setup), [&](){ ht->setup(); })();
2714 [ # # # # ]: 0 : ht->for_each([&, pipeline=std::move(pipeline)](HashTable::const_entry_t entry){
2715 : : /*----- Compute key schema to detect duplicated keys. -----*/
2716 : 0 : Schema key_schema;
2717 [ # # ]: 0 : for (std::size_t i = 0; i < num_keys; ++i) {
2718 [ # # ]: 0 : auto &e = M.grouping.schema()[i];
2719 [ # # # # ]: 0 : key_schema.add(e.id, e.type, e.constraints);
2720 : 0 : }
2721 : :
2722 : : /*----- Add computed group tuples to current environment. ----*/
2723 [ # # # # ]: 0 : for (auto &e : M.grouping.schema().deduplicate()) {
2724 : : try {
2725 [ # # ]: 0 : key_schema.find(e.id);
2726 [ # # ]: 0 : } catch (invalid_argument&) {
2727 : : continue; // skip duplicated keys since they must not be used afterwards
2728 [ # # ]: 0 : }
2729 : :
2730 [ # # # # ]: 0 : if (auto it = avg_aggregates.find(e.id);
2731 [ # # ]: 0 : it != avg_aggregates.end() and not it->second.compute_running_avg)
2732 : : { // AVG aggregates which is not yet computed, divide computed sum with computed count
2733 : 0 : auto &avg_info = it->second;
2734 [ # # ]: 0 : auto sum = std::visit(overloaded {
2735 : 0 : [&]<sql_type T>(HashTable::const_reference_t<T> &&r) -> _Doublex1
2736 : : requires (std::same_as<T, _I64x1> or std::same_as<T, _Doublex1>) {
2737 [ # # # # ]: 0 : return T(r).template to<double>();
2738 : 0 : },
2739 : 0 : [](auto&&) -> _Doublex1 { M_unreachable("invalid type"); },
2740 : 0 : [](std::monostate&&) -> _Doublex1 { M_unreachable("invalid reference"); },
2741 [ # # ]: 0 : }, entry.get(avg_info.sum));
2742 [ # # # # : 0 : auto count = _I64x1(entry.get<_I64x1>(avg_info.running_count)).insist_not_null().to<double>();
# # # # ]
2743 [ # # ]: 0 : auto avg = sum / count;
2744 [ # # ]: 0 : if (avg.can_be_null()) {
2745 [ # # ]: 0 : _Var<Doublex1> var(avg); // introduce variable s.t. uses only load from it
2746 [ # # # # : 0 : env.add(e.id, var);
# # ]
2747 : 0 : } else {
2748 : : /* introduce variable w/o NULL bit s.t. uses only load from it */
2749 [ # # # # ]: 0 : Var<Doublex1> var(avg.insist_not_null());
2750 [ # # # # : 0 : env.add(e.id, _Doublex1(var));
# # # # ]
2751 : 0 : }
2752 : 0 : } else { // part of key or already computed aggregate
2753 [ # # ]: 0 : std::visit(overloaded {
2754 : 0 : [&]<typename T>(HashTable::const_reference_t<Expr<T>> &&r) -> void {
2755 : 0 : Expr<T> value = r;
2756 [ # # # # : 0 : if (value.can_be_null()) {
# # # # #
# # # #
# ]
2757 [ # # # # : 0 : Var<Expr<T>> var(value); // introduce variable s.t. uses only load from it
# # # # #
# # # #
# ]
2758 [ # # # # : 0 : env.add(e.id, var);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
2759 : 0 : } else {
2760 : : /* introduce variable w/o NULL bit s.t. uses only load from it */
2761 [ # # # # : 0 : Var<PrimitiveExpr<T>> var(value.insist_not_null());
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
2762 [ # # # # : 0 : env.add(e.id, Expr<T>(var));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
2763 : 0 : }
2764 : 0 : },
2765 : 0 : [&](HashTable::const_reference_t<NChar> &&r) -> void {
2766 : 0 : NChar value(r);
2767 [ # # # # ]: 0 : Var<Ptr<Charx1>> var(value.val()); // introduce variable s.t. uses only load from it
2768 [ # # # # : 0 : env.add(e.id, NChar(var, value.can_be_null(), value.length(),
# # # # #
# ]
2769 : 0 : value.guarantees_terminating_nul()));
2770 : 0 : },
2771 : 0 : [](std::monostate&&) -> void { M_unreachable("invalid reference"); },
2772 [ # # ]: 0 : }, entry.get(e.id)); // do not extract to be able to access for not-yet-computed AVG aggregates
2773 : : }
2774 : : }
2775 : :
2776 : : /*----- Resume pipeline. -----*/
2777 [ # # ]: 0 : pipeline();
2778 : 0 : });
2779 [ # # # # ]: 0 : teardown_t(std::move(teardown), [&](){ ht->teardown(); })();
2780 : 0 : }
2781 : :
2782 : 0 : ConditionSet OrderedGrouping::pre_condition(
2783 : : std::size_t child_idx,
2784 : : const std::tuple<const GroupingOperator*> &partial_inner_nodes)
2785 : : {
2786 : 0 : M_insist(child_idx == 0);
2787 : :
2788 : 0 : ConditionSet pre_cond;
2789 : :
2790 : : /*----- Ordered grouping needs the data sorted on the grouping key (in either order). -----*/
2791 : 0 : Sortedness::order_t orders;
2792 [ # # # # ]: 0 : for (auto &p : std::get<0>(partial_inner_nodes)->group_by()) {
2793 [ # # ]: 0 : Schema::Identifier id(p.first);
2794 [ # # # # : 0 : if (orders.find(id) == orders.cend())
# # ]
2795 [ # # # # ]: 0 : orders.add(std::move(id), Sortedness::O_UNDEF);
2796 : 0 : }
2797 [ # # # # ]: 0 : pre_cond.add_condition(Sortedness(std::move(orders)));
2798 : :
2799 : : /*----- Ordered grouping does not support SIMD. -----*/
2800 [ # # # # ]: 0 : pre_cond.add_condition(NoSIMD());
2801 : :
2802 : 0 : return pre_cond;
2803 [ # # ]: 0 : }
2804 : :
2805 : 0 : double OrderedGrouping::cost(const Match<OrderedGrouping> &M)
2806 : : {
2807 : 0 : return 1.0 * M.child->get_matched_root().info().estimated_cardinality;
2808 : : }
2809 : :
2810 : 0 : ConditionSet OrderedGrouping::adapt_post_condition(const Match<OrderedGrouping> &M, const ConditionSet &post_cond_child)
2811 : : {
2812 : 0 : ConditionSet post_cond;
2813 : :
2814 : : /*----- Ordered grouping does not introduce predication. -----*/
2815 [ # # # # ]: 0 : post_cond.add_condition(Predicated(false));
2816 : :
2817 : : /*----- Preserve order of child for grouping keys. -----*/
2818 : 0 : Sortedness::order_t orders;
2819 [ # # ]: 0 : const auto &sortedness_child = post_cond_child.get_condition<Sortedness>();
2820 [ # # # # ]: 0 : for (auto &[expr, alias] : M.grouping.group_by()) {
2821 [ # # # # : 0 : auto it = sortedness_child.orders().find(Schema::Identifier(expr));
# # ]
2822 [ # # # # : 0 : M_insist(it != sortedness_child.orders().cend());
# # ]
2823 [ # # # # : 0 : Schema::Identifier id = alias.has_value() ? Schema::Identifier(alias.assert_not_none())
# # # # #
# # # ]
2824 [ # # ]: 0 : : Schema::Identifier(expr);
2825 [ # # # # : 0 : if (orders.find(id) == orders.cend())
# # ]
2826 [ # # # # ]: 0 : orders.add(std::move(id), it->second); // drop duplicate since it must not be used afterwards
2827 : 0 : }
2828 [ # # # # ]: 0 : post_cond.add_condition(Sortedness(std::move(orders)));
2829 : :
2830 : : /*----- Ordered grouping does not introduce SIMD. -----*/
2831 [ # # # # ]: 0 : post_cond.add_condition(NoSIMD());
2832 : :
2833 : 0 : return post_cond;
2834 [ # # ]: 0 : }
2835 : :
2836 : 0 : void OrderedGrouping::execute(const Match<OrderedGrouping> &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
2837 : : {
2838 : 0 : Environment results; ///< stores current result tuple
2839 [ # # ]: 0 : const auto num_keys = M.grouping.group_by().size();
2840 : :
2841 : : /*----- Compute key schema to detect duplicated keys. -----*/
2842 : 0 : Schema key_schema;
2843 [ # # ]: 0 : for (std::size_t i = 0; i < num_keys; ++i) {
2844 [ # # # # ]: 0 : auto &e = M.grouping.schema()[i];
2845 [ # # # # ]: 0 : key_schema.add(e.id, e.type, e.constraints);
2846 : 0 : }
2847 : :
2848 : : /*----- Compute information about aggregates, especially about AVG aggregates. -----*/
2849 [ # # # # : 0 : auto p = compute_aggregate_info(M.grouping.aggregates(), M.grouping.schema(), num_keys);
# # ]
2850 : 0 : const auto &aggregates = p.first;
2851 : 0 : const auto &avg_aggregates = p.second;
2852 : :
2853 : : /*----- Forward declare function to emit a group tuple in the current environment and resume the pipeline. -----*/
2854 [ # # ]: 0 : FunctionProxy<void(void)> emit_group_and_resume_pipeline("emit_group_and_resume_pipeline");
2855 : :
2856 : 0 : std::optional<Var<Boolx1>> first_iteration; ///< variable to *locally* check for first iteration
2857 : : ///> *global* flag backup since the following code may be called multiple times
2858 [ # # ]: 0 : Global<Boolx1> first_iteration_backup(true);
2859 : :
2860 : : using agg_t = agg_t_<false>;
2861 : : using agg_backup_t = agg_t_<true>;
2862 [ # # # # : 0 : agg_t agg_values[aggregates.size()]; ///< *local* values of the computed aggregates
# # ]
2863 [ # # # # : 0 : agg_backup_t agg_value_backups[aggregates.size()]; ///< *global* value backups of the computed aggregates
# # ]
2864 : :
2865 : : using key_t = key_t_<false>;
2866 : : using key_backup_t = key_t_<true>;
2867 [ # # # # : 0 : key_t key_values[num_keys]; ///< *local* values of the computed keys
# # ]
2868 [ # # # # : 0 : key_backup_t key_value_backups[num_keys]; ///< *global* value backups of the computed keys
# # ]
2869 : :
2870 : 0 : auto store_locals_to_globals = [&](){
2871 : : /*----- Store local aggregate values to globals to access them in other function. -----*/
2872 [ # # ]: 0 : for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
2873 : 0 : auto &info = aggregates[idx];
2874 : :
2875 [ # # # # : 0 : switch (info.fnid) {
# ]
2876 : : default:
2877 : 0 : M_unreachable("unsupported aggregate function");
2878 : : case m::Function::FN_MIN:
2879 : : case m::Function::FN_MAX: {
2880 : 0 : auto min_max = [&]<typename T>() {
2881 : 0 : auto &[min_max, is_null] = *M_notnull((
2882 : : std::get_if<std::pair<Var<PrimitiveExpr<T>>, std::optional<Var<Boolx1>>>>(&agg_values[idx])
2883 : : ));
2884 : 0 : auto &[min_max_backup, is_null_backup] = *M_notnull((
2885 : : std::get_if<std::pair<Global<PrimitiveExpr<T>>,
2886 : : std::optional<Global<Boolx1>>>>(&agg_value_backups[idx])
2887 : : ));
2888 : 0 : M_insist(bool(is_null) == bool(is_null_backup));
2889 : :
2890 : 0 : min_max_backup = min_max;
2891 [ # # # # : 0 : if (is_null)
# # # # #
# # # ]
2892 : 0 : *is_null_backup = *is_null;
2893 : 0 : };
2894 : 0 : auto &n = as<const Numeric>(*info.entry.type);
2895 [ # # # ]: 0 : switch (n.kind) {
2896 : : case Numeric::N_Int:
2897 : : case Numeric::N_Decimal:
2898 [ # # # # : 0 : switch (n.size()) {
# ]
2899 : 0 : default: M_unreachable("invalid size");
2900 : 0 : case 8: min_max.template operator()<int8_t >(); break;
2901 : 0 : case 16: min_max.template operator()<int16_t>(); break;
2902 : 0 : case 32: min_max.template operator()<int32_t>(); break;
2903 : 0 : case 64: min_max.template operator()<int64_t>(); break;
2904 : : }
2905 : 0 : break;
2906 : : case Numeric::N_Float:
2907 [ # # ]: 0 : if (n.size() <= 32)
2908 : 0 : min_max.template operator()<float>();
2909 : : else
2910 : 0 : min_max.template operator()<double>();
2911 : 0 : }
2912 : 0 : break;
2913 : : }
2914 : : case m::Function::FN_AVG: {
2915 : 0 : auto &[avg, is_null] = *M_notnull((
2916 : : std::get_if<std::pair<Var<Doublex1>, std::optional<Var<Boolx1>>>>(&agg_values[idx])
2917 : : ));
2918 : 0 : auto &[avg_backup, is_null_backup] = *M_notnull((
2919 : : std::get_if<std::pair<Global<Doublex1>, std::optional<Global<Boolx1>>>>(&agg_value_backups[idx])
2920 : : ));
2921 : 0 : M_insist(bool(is_null) == bool(is_null_backup));
2922 : :
2923 : 0 : avg_backup = avg;
2924 [ # # ]: 0 : if (is_null)
2925 : 0 : *is_null_backup = *is_null;
2926 : :
2927 : 0 : break;
2928 : : }
2929 : : case m::Function::FN_SUM: {
2930 : 0 : M_insist(info.args.size() == 1, "SUM aggregate function expects exactly one argument");
2931 : 0 : const auto &arg = *info.args[0];
2932 : :
2933 : 0 : auto sum = [&]<typename T>() {
2934 : 0 : auto &[sum, is_null] = *M_notnull((
2935 : : std::get_if<std::pair<Var<PrimitiveExpr<T>>, std::optional<Var<Boolx1>>>>(&agg_values[idx])
2936 : : ));
2937 : 0 : auto &[sum_backup, is_null_backup] = *M_notnull((
2938 : : std::get_if<std::pair<Global<PrimitiveExpr<T>>,
2939 : : std::optional<Global<Boolx1>>>>(&agg_value_backups[idx])
2940 : : ));
2941 : 0 : M_insist(bool(is_null) == bool(is_null_backup));
2942 : :
2943 : 0 : sum_backup = sum;
2944 [ # # # # : 0 : if (is_null)
# # # # #
# # # ]
2945 : 0 : *is_null_backup = *is_null;
2946 : 0 : };
2947 : 0 : auto &n = as<const Numeric>(*info.entry.type);
2948 [ # # # ]: 0 : switch (n.kind) {
2949 : : case Numeric::N_Int:
2950 : : case Numeric::N_Decimal:
2951 [ # # # # : 0 : switch (n.size()) {
# ]
2952 : 0 : default: M_unreachable("invalid size");
2953 : 0 : case 8: sum.template operator()<int8_t >(); break;
2954 : 0 : case 16: sum.template operator()<int16_t>(); break;
2955 : 0 : case 32: sum.template operator()<int32_t>(); break;
2956 : 0 : case 64: sum.template operator()<int64_t>(); break;
2957 : : }
2958 : 0 : break;
2959 : : case Numeric::N_Float:
2960 [ # # ]: 0 : if (n.size() <= 32)
2961 : 0 : sum.template operator()<float>();
2962 : : else
2963 : 0 : sum.template operator()<double>();
2964 : 0 : }
2965 : 0 : break;
2966 : : }
2967 : : case m::Function::FN_COUNT: {
2968 : 0 : auto &count = *M_notnull(std::get_if<Var<I64x1>>(&agg_values[idx]));
2969 : 0 : auto &count_backup = *M_notnull(std::get_if<Global<I64x1>>(&agg_value_backups[idx]));
2970 : :
2971 : 0 : count_backup = count;
2972 : :
2973 : 0 : break;
2974 : : }
2975 : : }
2976 : 0 : }
2977 : :
2978 : : /*----- Store local key values to globals to access them in other function. -----*/
2979 : 0 : auto store = [&]<typename T>(std::size_t idx) {
2980 : 0 : auto &[key, is_null] = *M_notnull((
2981 : : std::get_if<std::pair<Var<PrimitiveExpr<T>>, std::optional<Var<Boolx1>>>>(&key_values[idx])
2982 : : ));
2983 : 0 : auto &[key_backup, is_null_backup] = *M_notnull((
2984 : : std::get_if<std::pair<Global<PrimitiveExpr<T>>, std::optional<Global<Boolx1>>>>(&key_value_backups[idx])
2985 : : ));
2986 : 0 : M_insist(bool(is_null) == bool(is_null_backup));
2987 : :
2988 : 0 : key_backup = key;
2989 [ # # # # : 0 : if (is_null)
# # # # #
# # # #
# ]
2990 : 0 : *is_null_backup = *is_null;
2991 : 0 : };
2992 [ # # ]: 0 : for (std::size_t idx = 0; idx < num_keys; ++idx) {
2993 : 0 : visit(overloaded{
2994 : 0 : [&](const Boolean&) { store.template operator()<bool>(idx); },
2995 : 0 : [&](const Numeric &n) {
2996 [ # # # ]: 0 : switch (n.kind) {
2997 : : case Numeric::N_Int:
2998 : : case Numeric::N_Decimal:
2999 [ # # # # : 0 : switch (n.size()) {
# ]
3000 : 0 : default: M_unreachable("invalid size");
3001 : 0 : case 8: store.template operator()<int8_t >(idx); break;
3002 : 0 : case 16: store.template operator()<int16_t>(idx); break;
3003 : 0 : case 32: store.template operator()<int32_t>(idx); break;
3004 : 0 : case 64: store.template operator()<int64_t>(idx); break;
3005 : : }
3006 : 0 : break;
3007 : : case Numeric::N_Float:
3008 [ # # ]: 0 : if (n.size() <= 32)
3009 : 0 : store.template operator()<float>(idx);
3010 : : else
3011 : 0 : store.template operator()<double>(idx);
3012 : 0 : }
3013 : 0 : },
3014 : 0 : [&](const CharacterSequence &cs) {
3015 : 0 : auto &key = *M_notnull(std::get_if<Var<Ptr<Charx1>>>(&key_values[idx]));
3016 : 0 : auto &key_backup = *M_notnull(std::get_if<Global<Ptr<Charx1>>>(&key_value_backups[idx]));
3017 : :
3018 : 0 : key_backup = key;
3019 : 0 : },
3020 : 0 : [&](const Date&) { store.template operator()<int32_t>(idx); },
3021 : 0 : [&](const DateTime&) { store.template operator()<int64_t>(idx); },
3022 : 0 : [](auto&&) { M_unreachable("invalid type"); },
3023 : 0 : }, *M.grouping.schema()[idx].type);
3024 : 0 : }
3025 : 0 : };
3026 : :
3027 [ # # ]: 0 : M.child->execute(
3028 [ # # # # ]: 0 : /* setup= */ setup_t::Make_Without_Parent([&](){
3029 : 0 : first_iteration.emplace(first_iteration_backup);
3030 : :
3031 : : /*----- Initialize aggregates and their backups. -----*/
3032 [ # # ]: 0 : for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
3033 : 0 : auto &info = aggregates[idx];
3034 : 0 : const bool nullable = info.entry.nullable();
3035 : :
3036 : 0 : bool is_min = false; ///< flag to indicate whether aggregate function is MIN
3037 [ # # # # : 0 : switch (info.fnid) {
# # ]
3038 : : default:
3039 : 0 : M_unreachable("unsupported aggregate function");
3040 : : case m::Function::FN_MIN:
3041 : 0 : is_min = true; // set flag and delegate to MAX case
3042 : : case m::Function::FN_MAX: {
3043 : 0 : auto min_max = [&]<typename T>() {
3044 : 0 : auto neutral = is_min ? std::numeric_limits<T>::max()
3045 : 0 : : std::numeric_limits<T>::lowest();
3046 : :
3047 : 0 : Var<PrimitiveExpr<T>> min_max;
3048 [ # # # # : 0 : Global<PrimitiveExpr<T>> min_max_backup(neutral); // initialize with neutral element +inf or -inf
# # # # #
# # # ]
3049 : 0 : std::optional<Var<Boolx1>> is_null;
3050 : 0 : std::optional<Global<Boolx1>> is_null_backup;
3051 : :
3052 : : /*----- Set local aggregate variables to global backups. -----*/
3053 [ # # # # : 0 : min_max = min_max_backup;
# # # # #
# # # ]
3054 [ # # # # : 0 : if (nullable) {
# # # # #
# # # ]
3055 [ # # # # : 0 : is_null_backup.emplace(true); // MIN/MAX is initially NULL
# # # # #
# # # ]
3056 [ # # # # : 0 : is_null.emplace(*is_null_backup);
# # # # #
# # # ]
3057 : 0 : }
3058 : :
3059 : : /*----- Add global aggregate to result environment to access it in other function. -----*/
3060 [ # # # # : 0 : if (nullable)
# # # # #
# # # ]
3061 [ # # # # : 0 : results.add(info.entry.id, Select(*is_null_backup, Expr<T>::Null(), min_max_backup));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
3062 : : else
3063 [ # # # # : 0 : results.add(info.entry.id, min_max_backup.val());
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
3064 : :
3065 : : /*----- Move aggregate variables to access them later. ----*/
3066 [ # # # # : 0 : new (&agg_values[idx]) agg_t(std::make_pair(std::move(min_max), std::move(is_null)));
# # # # #
# # # # #
# # # # #
# # # #
# ]
3067 [ # # # # : 0 : new (&agg_value_backups[idx]) agg_backup_t(std::make_pair(
# # # # #
# # # # #
# # # # #
# # # #
# ]
3068 : : std::move(min_max_backup), std::move(is_null_backup)
3069 : : ));
3070 : 0 : };
3071 : 0 : auto &n = as<const Numeric>(*info.entry.type);
3072 [ # # # ]: 0 : switch (n.kind) {
3073 : : case Numeric::N_Int:
3074 : : case Numeric::N_Decimal:
3075 [ # # # # : 0 : switch (n.size()) {
# ]
3076 : 0 : default: M_unreachable("invalid size");
3077 : 0 : case 8: min_max.template operator()<int8_t >(); break;
3078 : 0 : case 16: min_max.template operator()<int16_t>(); break;
3079 : 0 : case 32: min_max.template operator()<int32_t>(); break;
3080 : 0 : case 64: min_max.template operator()<int64_t>(); break;
3081 : : }
3082 : 0 : break;
3083 : : case Numeric::N_Float:
3084 [ # # ]: 0 : if (n.size() <= 32)
3085 : 0 : min_max.template operator()<float>();
3086 : : else
3087 : 0 : min_max.template operator()<double>();
3088 : 0 : }
3089 : 0 : break;
3090 : : }
3091 : : case m::Function::FN_AVG: {
3092 : 0 : Var<Doublex1> avg;
3093 [ # # ]: 0 : Global<Doublex1> avg_backup(0.0); // initialize with neutral element 0
3094 : 0 : std::optional<Var<Boolx1>> is_null;
3095 : 0 : std::optional<Global<Boolx1>> is_null_backup;
3096 : :
3097 : : /*----- Set local aggregate variables to global backups. -----*/
3098 [ # # ]: 0 : avg = avg_backup;
3099 [ # # ]: 0 : if (nullable) {
3100 [ # # ]: 0 : is_null_backup.emplace(true); // AVG is initially NULL
3101 [ # # ]: 0 : is_null.emplace(*is_null_backup);
3102 : 0 : }
3103 : :
3104 : : /*----- Add global aggregate to result environment to access it in other function. -----*/
3105 [ # # ]: 0 : if (nullable)
3106 [ # # # # : 0 : results.add(info.entry.id, Select(*is_null_backup, _Doublex1::Null(), avg_backup));
# # # # ]
3107 : : else
3108 [ # # # # : 0 : results.add(info.entry.id, avg_backup.val());
# # # # ]
3109 : :
3110 : : /*----- Move aggregate variables to access them later. ----*/
3111 [ # # # # ]: 0 : new (&agg_values[idx]) agg_t(std::make_pair(std::move(avg), std::move(is_null)));
3112 [ # # # # ]: 0 : new (&agg_value_backups[idx]) agg_backup_t(std::make_pair(
3113 : : std::move(avg_backup), std::move(is_null_backup)
3114 : : ));
3115 : :
3116 : : break;
3117 : 0 : }
3118 : : case m::Function::FN_SUM: {
3119 : 0 : auto sum = [&]<typename T>() {
3120 : 0 : Var<PrimitiveExpr<T>> sum;
3121 [ # # # # : 0 : Global<PrimitiveExpr<T>> sum_backup(T(0)); // initialize with neutral element 0
# # # # #
# # # ]
3122 : 0 : std::optional<Var<Boolx1>> is_null;
3123 : 0 : std::optional<Global<Boolx1>> is_null_backup;
3124 : :
3125 : : /*----- Set local aggregate variables to global backups. -----*/
3126 [ # # # # : 0 : sum = sum_backup;
# # # # #
# # # ]
3127 [ # # # # : 0 : if (nullable) {
# # # # #
# # # ]
3128 [ # # # # : 0 : is_null_backup.emplace(true); // SUM is initially NULL
# # # # #
# # # ]
3129 [ # # # # : 0 : is_null.emplace(*is_null_backup);
# # # # #
# # # ]
3130 : 0 : }
3131 : :
3132 : : /*----- Add global aggregate to result environment to access it in other function. -----*/
3133 [ # # # # : 0 : if (nullable)
# # # # #
# # # ]
3134 [ # # # # : 0 : results.add(info.entry.id, Select(*is_null_backup, Expr<T>::Null(), sum_backup));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
3135 : : else
3136 [ # # # # : 0 : results.add(info.entry.id, sum_backup.val());
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
3137 : :
3138 : : /*----- Move aggregate variables to access them later. ----*/
3139 [ # # # # : 0 : new (&agg_values[idx]) agg_t(std::make_pair(std::move(sum), std::move(is_null)));
# # # # #
# # # # #
# # # # #
# # # #
# ]
3140 [ # # # # : 0 : new (&agg_value_backups[idx]) agg_backup_t(std::make_pair(
# # # # #
# # # # #
# # # # #
# # # #
# ]
3141 : : std::move(sum_backup), std::move(is_null_backup)
3142 : : ));
3143 : 0 : };
3144 : 0 : auto &n = as<const Numeric>(*info.entry.type);
3145 [ # # # ]: 0 : switch (n.kind) {
3146 : : case Numeric::N_Int:
3147 : : case Numeric::N_Decimal:
3148 [ # # # # : 0 : switch (n.size()) {
# ]
3149 : 0 : default: M_unreachable("invalid size");
3150 : 0 : case 8: sum.template operator()<int8_t >(); break;
3151 : 0 : case 16: sum.template operator()<int16_t>(); break;
3152 : 0 : case 32: sum.template operator()<int32_t>(); break;
3153 : 0 : case 64: sum.template operator()<int64_t>(); break;
3154 : : }
3155 : 0 : break;
3156 : : case Numeric::N_Float:
3157 [ # # ]: 0 : if (n.size() <= 32)
3158 : 0 : sum.template operator()<float>();
3159 : : else
3160 : 0 : sum.template operator()<double>();
3161 : 0 : }
3162 : 0 : break;
3163 : : }
3164 : : case m::Function::FN_COUNT: {
3165 : 0 : Var<I64x1> count;
3166 [ # # ]: 0 : Global<I64x1> count_backup(0); // initialize with neutral element 0
3167 : : /* no `is_null` variables needed since COUNT will not be NULL */
3168 : :
3169 : : /*----- Set local aggregate variable to global backup. -----*/
3170 [ # # ]: 0 : count = count_backup;
3171 : :
3172 : : /*----- Add global aggregate to result environment to access it in other function. -----*/
3173 [ # # # # : 0 : results.add(info.entry.id, count_backup.val());
# # # # ]
3174 : :
3175 : : /*----- Move aggregate variables to access them later. ----*/
3176 [ # # ]: 0 : new (&agg_values[idx]) agg_t(std::move(count));
3177 [ # # ]: 0 : new (&agg_value_backups[idx]) agg_backup_t(std::move(count_backup));
3178 : :
3179 : : break;
3180 : 0 : }
3181 : : }
3182 : 0 : }
3183 : :
3184 : : /*----- Initialize keys and their backups. -----*/
3185 : 0 : auto init = [&]<typename T>(std::size_t idx) {
3186 : 0 : const bool nullable = M.grouping.schema()[idx].nullable();
3187 : :
3188 : 0 : Var<PrimitiveExpr<T>> key;
3189 [ # # # # : 0 : Global<PrimitiveExpr<T>> key_backup;
# # # # #
# # # #
# ]
3190 : 0 : std::optional<Var<Boolx1>> is_null;
3191 : 0 : std::optional<Global<Boolx1>> is_null_backup;
3192 : :
3193 : : /*----- Set local key variables to global backups. -----*/
3194 [ # # # # : 0 : key = key_backup;
# # # # #
# # # #
# ]
3195 [ # # # # : 0 : if (nullable) {
# # # # #
# # # #
# ]
3196 [ # # # # : 0 : is_null_backup.emplace();
# # # # #
# # # #
# ]
3197 [ # # # # : 0 : is_null.emplace(*is_null_backup);
# # # # #
# # # #
# ]
3198 : 0 : }
3199 : :
3200 : : try {
3201 [ # # # # : 0 : auto id = M.grouping.schema()[idx].id;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
3202 [ # # # # : 0 : key_schema.find(id);
# # # # #
# # # #
# ]
3203 : :
3204 : : /*----- Add global key to result environment to access it in other function. -----*/
3205 [ # # # # : 0 : if (nullable)
# # # # #
# # # #
# ]
3206 [ # # # # : 0 : results.add(id, Select(*is_null_backup, Expr<T>::Null(), key_backup));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
3207 : : else
3208 [ # # # # : 0 : results.add(id, key_backup.val());
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
3209 [ # # # # : 0 : } catch (invalid_argument&) {
# # # # #
# # # #
# ]
3210 : : /* skip adding to result environment for duplicate keys since they must not be used afterwards */
3211 [ # # # # : 0 : }
# # # # #
# # # #
# ]
3212 : :
3213 : : /*----- Move key variables to access them later. ----*/
3214 [ # # # # : 0 : new (&key_values[idx]) key_t(std::make_pair(std::move(key), std::move(is_null)));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
3215 [ # # # # : 0 : new (&key_value_backups[idx]) key_backup_t(std::make_pair(
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
3216 : : std::move(key_backup), std::move(is_null_backup)
3217 : : ));
3218 : 0 : };
3219 [ # # ]: 0 : for (std::size_t idx = 0; idx < num_keys; ++idx) {
3220 : 0 : visit(overloaded{
3221 : 0 : [&](const Boolean&) { init.template operator()<bool>(idx); },
3222 : 0 : [&](const Numeric &n) {
3223 [ # # # ]: 0 : switch (n.kind) {
3224 : : case Numeric::N_Int:
3225 : : case Numeric::N_Decimal:
3226 [ # # # # : 0 : switch (n.size()) {
# ]
3227 : 0 : default: M_unreachable("invalid size");
3228 : 0 : case 8: init.template operator()<int8_t >(idx); break;
3229 : 0 : case 16: init.template operator()<int16_t>(idx); break;
3230 : 0 : case 32: init.template operator()<int32_t>(idx); break;
3231 : 0 : case 64: init.template operator()<int64_t>(idx); break;
3232 : : }
3233 : 0 : break;
3234 : : case Numeric::N_Float:
3235 [ # # ]: 0 : if (n.size() <= 32)
3236 : 0 : init.template operator()<float>(idx);
3237 : : else
3238 : 0 : init.template operator()<double>(idx);
3239 : 0 : }
3240 : 0 : },
3241 : 0 : [&](const CharacterSequence &cs) {
3242 : 0 : Var<Ptr<Charx1>> key;
3243 [ # # ]: 0 : Global<Ptr<Charx1>> key_backup;
3244 : : /* no `is_null` variables needed since pointer types must not be NULL */
3245 : :
3246 : : /*----- Set local key variable to global backup. -----*/
3247 [ # # ]: 0 : key = key_backup;
3248 : :
3249 : : try {
3250 [ # # # # ]: 0 : auto id = M.grouping.schema()[idx].id;
3251 [ # # ]: 0 : key_schema.find(id);
3252 : :
3253 : : /*----- Add global key to result environment to access it in other function. -----*/
3254 [ # # # # : 0 : NChar str(key_backup.val(), M.grouping.schema()[idx].nullable(), cs.length, cs.is_varying);
# # ]
3255 [ # # # # ]: 0 : results.add(id, std::move(str));
3256 [ # # ]: 0 : } catch (invalid_argument&) {
3257 : : /* skip adding to result environment for duplicate keys since they must not be used
3258 : : * afterwards */
3259 [ # # ]: 0 : }
3260 : :
3261 : : /*----- Move key variables to access them later. ----*/
3262 [ # # ]: 0 : new (&key_values[idx]) key_t(std::move(key));
3263 [ # # ]: 0 : new (&key_value_backups[idx]) key_backup_t(std::move(key_backup));
3264 : 0 : },
3265 : 0 : [&](const Date&) { init.template operator()<int32_t>(idx); },
3266 : 0 : [&](const DateTime&) { init.template operator()<int64_t>(idx); },
3267 : 0 : [](auto&&) { M_unreachable("invalid type"); },
3268 : 0 : }, *M.grouping.schema()[idx].type);
3269 : 0 : }
3270 : 0 : }),
3271 [ # # ]: 0 : /* pipeline= */ [&](){
3272 : 0 : auto &env = CodeGenContext::Get().env();
3273 : :
3274 : : /*----- If predication is used, introduce pred. var. and update it before computing aggregates. -----*/
3275 : 0 : std::optional<Var<Boolx1>> pred;
3276 [ # # ]: 0 : if (env.predicated()) {
3277 [ # # # # ]: 0 : M_insist(CodeGenContext::Get().num_simd_lanes() == 1, "invalid number of SIMD lanes");
3278 [ # # # # : 0 : pred = env.extract_predicate<_Boolx1>().is_true_and_not_null();
# # ]
3279 : 0 : }
3280 : :
3281 : : /*----- Compute aggregates. -----*/
3282 [ # # ]: 0 : Block reset_aggs("ordered_grouping.reset_aggs", false),
3283 [ # # ]: 0 : update_aggs("ordered_grouping.update_aggs", false),
3284 [ # # ]: 0 : update_avg_aggs("ordered_grouping.update_avg_aggs", false);
3285 [ # # ]: 0 : for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
3286 : 0 : auto &info = aggregates[idx];
3287 : :
3288 : 0 : bool is_min = false; ///< flag to indicate whether aggregate function is MIN
3289 [ # # # # : 0 : switch (info.fnid) {
# # ]
3290 : : default:
3291 [ # # ]: 0 : M_unreachable("unsupported aggregate function");
3292 : : case m::Function::FN_MIN:
3293 : 0 : is_min = true; // set flag and delegate to MAX case
3294 : : case m::Function::FN_MAX: {
3295 [ # # ]: 0 : M_insist(info.args.size() == 1, "MIN and MAX aggregate functions expect exactly one argument");
3296 : 0 : const auto &arg = *info.args[0];
3297 : 0 : auto min_max = [&]<typename T>() {
3298 : 0 : auto neutral = is_min ? std::numeric_limits<T>::max()
3299 : 0 : : std::numeric_limits<T>::lowest();
3300 : :
3301 : 0 : auto &[min_max, is_null] = *M_notnull((
3302 : : std::get_if<std::pair<Var<PrimitiveExpr<T>>, std::optional<Var<Boolx1>>>>(&agg_values[idx])
3303 : : ));
3304 : :
3305 : 0 : BLOCK_OPEN(reset_aggs) {
3306 [ # # # # : 0 : min_max = neutral;
# # # # #
# # # ]
3307 [ # # # # : 0 : if (is_null)
# # # # #
# # # ]
3308 [ # # # # : 0 : is_null->set_true();
# # # # #
# # # ]
3309 : : }
3310 : :
3311 : 0 : BLOCK_OPEN(update_aggs) {
3312 [ # # # # : 0 : auto _arg = env.compile(arg);
# # # # #
# # # ]
3313 [ # # # # : 0 : Expr<T> _new_val = convert<Expr<T>>(_arg);
# # # # #
# # # ]
3314 [ # # # # : 0 : M_insist(_new_val.can_be_null() == bool(is_null));
# # # # #
# # # # #
# # # # #
# # # #
# ]
3315 [ # # # # : 0 : if (_new_val.can_be_null()) {
# # # # #
# # # ]
3316 : : M_insist_no_ternary_logic();
3317 [ # # # # : 0 : auto _new_val_pred = pred ? Select(*pred, _new_val, Expr<T>::Null()) : _new_val;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
3318 [ # # # # : 0 : auto [new_val_, new_val_is_null_] = _new_val_pred.split();
# # # # #
# # # ]
3319 [ # # # # : 0 : const Var<Boolx1> new_val_is_null(new_val_is_null_); // due to multiple uses
# # # # #
# # # ]
3320 : :
3321 : : if constexpr (std::floating_point<T>) {
3322 [ # # # # : 0 : min_max = Select(new_val_is_null,
# # # # ]
3323 : : min_max, // ignore NULL
3324 [ # # # # : 0 : is_min ? min(min_max, new_val_) // update old min with new value
# # # # ]
3325 [ # # # # ]: 0 : : max(min_max, new_val_)); // update old max with new value
3326 : : } else {
3327 [ # # # # : 0 : const Var<PrimitiveExpr<T>> new_val(new_val_); // due to multiple uses
# # # # ]
3328 [ # # # # : 0 : auto cmp = is_min ? new_val < min_max : new_val > min_max;
# # # # #
# # # # #
# # # # #
# # # #
# ]
3329 : : #if 1
3330 [ # # # # : 0 : min_max = Select(new_val_is_null,
# # # # #
# # # # #
# # ]
3331 : : min_max, // ignore NULL
3332 [ # # # # : 0 : Select(cmp,
# # # # ]
3333 : : new_val, // update to new value
3334 : : min_max)); // do not update
3335 : : #else
3336 : : IF (not new_val_is_null and cmp) {
3337 : : min_max = new_val;
3338 : : };
3339 : : #endif
3340 : 0 : }
3341 [ # # # # : 0 : *is_null = *is_null and new_val_is_null; // MIN/MAX is NULL iff all values are NULL
# # # # #
# # # # #
# # # # #
# # # #
# ]
3342 : 0 : } else {
3343 [ # # # # : 0 : auto _new_val_pred = pred ? Select(*pred, _new_val, neutral) : _new_val;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
3344 [ # # # # : 0 : auto new_val_ = _new_val_pred.insist_not_null();
# # # # #
# # # ]
3345 : : if constexpr (std::floating_point<T>) {
3346 [ # # # # : 0 : min_max = is_min ? min(min_max, new_val_) // update old min with new value
# # # # #
# # # ]
3347 [ # # # # ]: 0 : : max(min_max, new_val_); // update old max with new value
3348 : : } else {
3349 [ # # # # : 0 : const Var<PrimitiveExpr<T>> new_val(new_val_); // due to multiple uses
# # # # ]
3350 [ # # # # : 0 : auto cmp = is_min ? new_val < min_max : new_val > min_max;
# # # # #
# # # # #
# # # # #
# # # #
# ]
3351 : : #if 1
3352 [ # # # # : 0 : min_max = Select(cmp,
# # # # #
# # # # #
# # ]
3353 : : new_val, // update to new value
3354 : : min_max); // do not update
3355 : : #else
3356 : : IF (cmp) {
3357 : : min_max = new_val;
3358 : : };
3359 : : #endif
3360 : 0 : }
3361 : 0 : }
3362 : 0 : }
3363 : 0 : };
3364 [ # # ]: 0 : auto &n = as<const Numeric>(*info.entry.type);
3365 [ # # # ]: 0 : switch (n.kind) {
3366 : : case Numeric::N_Int:
3367 : : case Numeric::N_Decimal:
3368 [ # # # # : 0 : switch (n.size()) {
# # # ]
3369 [ # # ]: 0 : default: M_unreachable("invalid size");
3370 [ # # ]: 0 : case 8: min_max.template operator()<int8_t >(); break;
3371 [ # # ]: 0 : case 16: min_max.template operator()<int16_t>(); break;
3372 [ # # ]: 0 : case 32: min_max.template operator()<int32_t>(); break;
3373 [ # # ]: 0 : case 64: min_max.template operator()<int64_t>(); break;
3374 : : }
3375 : 0 : break;
3376 : : case Numeric::N_Float:
3377 [ # # # # ]: 0 : if (n.size() <= 32)
3378 [ # # ]: 0 : min_max.template operator()<float>();
3379 : : else
3380 [ # # ]: 0 : min_max.template operator()<double>();
3381 : 0 : }
3382 : 0 : break;
3383 : : }
3384 : : case m::Function::FN_AVG:
3385 : 0 : break; // skip here and handle later
3386 : : case m::Function::FN_SUM: {
3387 [ # # ]: 0 : M_insist(info.args.size() == 1, "SUM aggregate function expects exactly one argument");
3388 : 0 : const auto &arg = *info.args[0];
3389 : :
3390 : 0 : auto sum = [&]<typename T>() {
3391 : 0 : auto &[sum, is_null] = *M_notnull((
3392 : : std::get_if<std::pair<Var<PrimitiveExpr<T>>, std::optional<Var<Boolx1>>>>(&agg_values[idx])
3393 : : ));
3394 : :
3395 : 0 : BLOCK_OPEN(reset_aggs) {
3396 [ # # # # : 0 : sum = T(0);
# # # # #
# # # # #
# # # # #
# # # #
# ]
3397 [ # # # # : 0 : if (is_null)
# # # # #
# # # ]
3398 [ # # # # : 0 : is_null->set_true();
# # # # #
# # # ]
3399 : : }
3400 : :
3401 : 0 : BLOCK_OPEN(update_aggs) {
3402 [ # # # # : 0 : auto _arg = env.compile(arg);
# # # # #
# # # ]
3403 [ # # # # : 0 : Expr<T> _new_val = convert<Expr<T>>(_arg);
# # # # #
# # # ]
3404 [ # # # # : 0 : M_insist(_new_val.can_be_null() == bool(is_null));
# # # # #
# # # # #
# # # # #
# # # #
# ]
3405 [ # # # # : 0 : if (_new_val.can_be_null()) {
# # # # #
# # # ]
3406 : : M_insist_no_ternary_logic();
3407 [ # # # # : 0 : auto _new_val_pred = pred ? Select(*pred, _new_val, Expr<T>::Null()) : _new_val;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
3408 [ # # # # : 0 : auto [new_val, new_val_is_null_] = _new_val_pred.split();
# # # # #
# # # ]
3409 [ # # # # : 0 : const Var<Boolx1> new_val_is_null(new_val_is_null_); // due to multiple uses
# # # # #
# # # ]
3410 : :
3411 [ # # # # : 0 : sum += Select(new_val_is_null,
# # # # #
# # # # #
# # # # #
# # # #
# ]
3412 : 0 : T(0), // ignore NULL
3413 : : new_val); // add new value to old sum
3414 [ # # # # : 0 : *is_null = *is_null and new_val_is_null; // SUM is NULL iff all values are NULL
# # # # #
# # # # #
# # # # #
# # # #
# ]
3415 : 0 : } else {
3416 [ # # # # : 0 : auto _new_val_pred = pred ? Select(*pred, _new_val, T(0)) : _new_val;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
3417 [ # # # # : 0 : sum += _new_val_pred.insist_not_null(); // add new value to old sum
# # # # #
# # # # #
# # # # #
# # # #
# ]
3418 : 0 : }
3419 : 0 : }
3420 : 0 : };
3421 [ # # ]: 0 : auto &n = as<const Numeric>(*info.entry.type);
3422 [ # # # ]: 0 : switch (n.kind) {
3423 : : case Numeric::N_Int:
3424 : : case Numeric::N_Decimal:
3425 [ # # # # : 0 : switch (n.size()) {
# # # ]
3426 [ # # ]: 0 : default: M_unreachable("invalid size");
3427 [ # # ]: 0 : case 8: sum.template operator()<int8_t >(); break;
3428 [ # # ]: 0 : case 16: sum.template operator()<int16_t>(); break;
3429 [ # # ]: 0 : case 32: sum.template operator()<int32_t>(); break;
3430 [ # # ]: 0 : case 64: sum.template operator()<int64_t>(); break;
3431 : : }
3432 : 0 : break;
3433 : : case Numeric::N_Float:
3434 [ # # # # ]: 0 : if (n.size() <= 32)
3435 [ # # ]: 0 : sum.template operator()<float>();
3436 : : else
3437 [ # # ]: 0 : sum.template operator()<double>();
3438 : 0 : }
3439 : 0 : break;
3440 : : }
3441 : : case m::Function::FN_COUNT: {
3442 [ # # ]: 0 : M_insist(info.args.size() <= 1, "COUNT aggregate function expects at most one argument");
3443 [ # # # # : 0 : M_insist(info.entry.type->is_integral() and info.entry.type->size() == 64);
# # ]
3444 : :
3445 [ # # ]: 0 : auto &count = *M_notnull(std::get_if<Var<I64x1>>(&agg_values[idx]));
3446 : :
3447 [ # # ]: 0 : BLOCK_OPEN(reset_aggs) {
3448 [ # # ]: 0 : count = int64_t(0);
3449 : : }
3450 : :
3451 [ # # ]: 0 : BLOCK_OPEN(update_aggs) {
3452 [ # # ]: 0 : if (info.args.empty()) {
3453 [ # # # # : 0 : count += pred ? pred->to<int64_t>() : I64x1(1); // increment old count by 1 iff `pred` is true
# # # # ]
3454 : 0 : } else {
3455 [ # # ]: 0 : auto _new_val = env.compile(*info.args[0]);
3456 [ # # # # ]: 0 : if (can_be_null(_new_val)) {
3457 : : M_insist_no_ternary_logic();
3458 [ # # # # : 0 : I64x1 inc = pred ? (not_null(_new_val) and *pred).to<int64_t>()
# # # # #
# # # # #
# # # # #
# ]
3459 [ # # # # ]: 0 : : not_null(_new_val).to<int64_t>();
3460 [ # # ]: 0 : count += inc; // increment old count by 1 iff new value is present and `pred` is true
3461 : 0 : } else {
3462 [ # # ]: 0 : discard(_new_val); // since it is not needed in this case
3463 [ # # # # : 0 : I64x1 inc = pred ? pred->to<int64_t>() : I64x1(1);
# # ]
3464 [ # # ]: 0 : count += inc; // increment old count by 1 iff new value is present and `pred` is true
3465 : 0 : }
3466 : 0 : }
3467 : : }
3468 : 0 : break;
3469 : : }
3470 : : }
3471 : 0 : }
3472 : :
3473 : : /*----- Compute AVG aggregates after others to ensure that running count is already created. -----*/
3474 [ # # ]: 0 : for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
3475 : 0 : auto &info = aggregates[idx];
3476 : :
3477 [ # # ]: 0 : if (info.fnid == m::Function::FN_AVG) {
3478 [ # # ]: 0 : M_insist(info.args.size() == 1, "AVG aggregate function expects exactly one argument");
3479 : 0 : const auto &arg = *info.args[0];
3480 [ # # # # ]: 0 : M_insist(info.entry.type->is_double());
3481 : :
3482 [ # # ]: 0 : auto it = avg_aggregates.find(info.entry.id);
3483 [ # # ]: 0 : M_insist(it != avg_aggregates.end());
3484 : 0 : const auto &avg_info = it->second;
3485 [ # # ]: 0 : M_insist(avg_info.compute_running_avg,
3486 : : "AVG aggregate may only occur for running average computations");
3487 : :
3488 [ # # ]: 0 : auto &[avg, is_null] = *M_notnull((
3489 : : std::get_if<std::pair<Var<Doublex1>, std::optional<Var<Boolx1>>>>(&agg_values[idx])
3490 : : ));
3491 : :
3492 [ # # ]: 0 : BLOCK_OPEN(reset_aggs) {
3493 [ # # # # ]: 0 : avg = 0.0;
3494 [ # # ]: 0 : if (is_null)
3495 [ # # ]: 0 : is_null->set_true();
3496 : : }
3497 : :
3498 [ # # ]: 0 : BLOCK_OPEN(update_avg_aggs) {
3499 : : /* Compute AVG as iterative mean as described in Knuth, The Art of Computer Programming
3500 : : * Vol 2, section 4.2.2. */
3501 [ # # ]: 0 : auto running_count_idx = std::distance(
3502 : 0 : aggregates.cbegin(),
3503 [ # # ]: 0 : std::find_if(aggregates.cbegin(), aggregates.cend(), [&avg_info](const auto &info){
3504 : 0 : return info.entry.id == avg_info.running_count;
3505 : : })
3506 : : );
3507 [ # # # # ]: 0 : M_insist(0 <= running_count_idx and running_count_idx < aggregates.size());
3508 [ # # ]: 0 : auto &running_count = *M_notnull(std::get_if<Var<I64x1>>(&agg_values[running_count_idx]));
3509 : :
3510 [ # # ]: 0 : auto _arg = env.compile(arg);
3511 [ # # ]: 0 : _Doublex1 _new_val = convert<_Doublex1>(_arg);
3512 [ # # # # ]: 0 : M_insist(_new_val.can_be_null() == bool(is_null));
3513 [ # # ]: 0 : if (_new_val.can_be_null()) {
3514 : : M_insist_no_ternary_logic();
3515 [ # # # # : 0 : auto _new_val_pred = pred ? Select(*pred, _new_val, _Doublex1::Null()) : _new_val;
# # # # #
# # # ]
3516 [ # # ]: 0 : auto [new_val, new_val_is_null_] = _new_val_pred.split();
3517 [ # # ]: 0 : const Var<Boolx1> new_val_is_null(new_val_is_null_); // due to multiple uses
3518 : :
3519 [ # # ]: 0 : auto delta_absolute = new_val - avg;
3520 [ # # # # ]: 0 : auto delta_relative = delta_absolute / running_count.to<double>();
3521 : :
3522 [ # # # # ]: 0 : avg += Select(new_val_is_null,
3523 : 0 : 0.0, // ignore NULL
3524 : : delta_relative); // update old average with new value
3525 [ # # # # ]: 0 : *is_null = *is_null and new_val_is_null; // AVG is NULL iff all values are NULL
3526 : 0 : } else {
3527 [ # # # # : 0 : auto _new_val_pred = pred ? Select(*pred, _new_val, avg) : _new_val;
# # # # ]
3528 [ # # # # ]: 0 : auto delta_absolute = _new_val_pred.insist_not_null() - avg;
3529 [ # # # # ]: 0 : auto delta_relative = delta_absolute / running_count.to<double>();
3530 : :
3531 [ # # ]: 0 : avg += delta_relative; // update old average with new value
3532 : 0 : }
3533 : 0 : }
3534 : 0 : }
3535 : 0 : }
3536 : :
3537 : : /*----- Compute whether new group starts and update key variables accordingly. -----*/
3538 : 0 : std::optional<Boolx1> group_differs;
3539 [ # # ]: 0 : Block update_keys("ordered_grouping.update_grouping_keys", false);
3540 [ # # ]: 0 : for (std::size_t idx = 0; idx < num_keys; ++idx) {
3541 [ # # ]: 0 : std::visit(overloaded {
3542 : 0 : [&]<typename T>(Expr<T> value) -> void {
3543 : 0 : auto &[key_val, key_is_null] = *M_notnull((
3544 : : std::get_if<std::pair<Var<PrimitiveExpr<T>>, std::optional<Var<Boolx1>>>>(&key_values[idx])
3545 : : ));
3546 : 0 : M_insist(value.can_be_null() == bool(key_is_null));
3547 : :
3548 [ # # # # : 0 : if (value.can_be_null()) {
# # # # #
# # # #
# ]
3549 : : M_insist_no_ternary_logic();
3550 [ # # # # : 0 : auto [val, is_null] = value.clone().split();
# # # # #
# # # #
# ]
3551 [ # # # # : 0 : auto null_differs = is_null != *key_is_null;
# # # # #
# # # #
# ]
3552 [ # # # # : 0 : Boolx1 key_differs = null_differs or (not *key_is_null and val != key_val);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
3553 [ # # # # : 0 : if (group_differs)
# # # # #
# # # #
# ]
3554 [ # # # # : 0 : group_differs.emplace(key_differs or *group_differs);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
3555 : : else
3556 [ # # # # : 0 : group_differs.emplace(key_differs);
# # # # #
# # # #
# ]
3557 : :
3558 [ # # # # : 0 : BLOCK_OPEN(update_keys) {
# # # # #
# # # #
# ]
3559 [ # # # # : 0 : std::tie(key_val, key_is_null) = value.split();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
3560 : : }
3561 : 0 : } else {
3562 [ # # # # : 0 : Boolx1 key_differs = key_val != value.clone().insist_not_null();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
3563 [ # # # # : 0 : if (group_differs)
# # # # #
# # # #
# ]
3564 [ # # # # : 0 : group_differs.emplace(key_differs or *group_differs);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
3565 : : else
3566 [ # # # # : 0 : group_differs.emplace(key_differs);
# # # # #
# # # #
# ]
3567 : :
3568 [ # # # # : 0 : BLOCK_OPEN(update_keys) {
# # # # #
# # # #
# ]
3569 [ # # # # : 0 : key_val = value.insist_not_null();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
3570 : : }
3571 : 0 : }
3572 : 0 : },
3573 : 0 : [&](NChar value) -> void {
3574 : 0 : auto &key = *M_notnull(std::get_if<Var<Ptr<Charx1>>>(&key_values[idx]));
3575 : :
3576 [ # # ]: 0 : auto [key_addr, key_is_nullptr] = key.val().split();
3577 [ # # # # : 0 : auto [addr, is_nullptr] = value.val().clone().split();
# # ]
3578 [ # # ]: 0 : auto addr_differs = strncmp(
3579 [ # # # # : 0 : /* left= */ NChar(addr, value.can_be_null(), value.length(),
# # ]
3580 : 0 : value.guarantees_terminating_nul()),
3581 [ # # # # : 0 : /* right= */ NChar(key_addr, value.can_be_null(), value.length(),
# # ]
3582 : 0 : value.guarantees_terminating_nul()),
3583 [ # # ]: 0 : /* len= */ U32x1(value.length()),
3584 : : /* op= */ NE
3585 : : );
3586 [ # # ]: 0 : auto [addr_differs_value, addr_differs_is_null] = addr_differs.split();
3587 [ # # ]: 0 : addr_differs_is_null.discard(); // use potentially-null value but it is overruled if it is NULL
3588 [ # # # # ]: 0 : auto nullptr_differs = is_nullptr != key_is_nullptr.clone();
3589 [ # # # # : 0 : Boolx1 key_differs = nullptr_differs or (not key_is_nullptr and addr_differs_value);
# # # # ]
3590 [ # # ]: 0 : if (group_differs)
3591 [ # # # # : 0 : group_differs.emplace(key_differs or *group_differs);
# # ]
3592 : : else
3593 [ # # ]: 0 : group_differs.emplace(key_differs);
3594 : :
3595 [ # # ]: 0 : BLOCK_OPEN(update_keys) {
3596 [ # # # # ]: 0 : key = value.val();
3597 : : }
3598 : 0 : },
3599 : 0 : [](auto) -> void { M_unreachable("SIMDfication currently not supported"); },
3600 : 0 : [](std::monostate) -> void { M_unreachable("invalid expression"); },
3601 [ # # ]: 0 : }, env.compile(M.grouping.group_by()[idx].first.get()));
3602 : 0 : }
3603 [ # # ]: 0 : M_insist(bool(group_differs));
3604 : :
3605 : : /*----- Resume pipeline with computed group iff new one starts and emit code to reset aggregates. ---*/
3606 [ # # ]: 0 : M_insist(bool(first_iteration));
3607 [ # # ]: 0 : Boolx1 cond = *first_iteration or *group_differs; // `group_differs` defaulted in first iteration but overruled anyway
3608 [ # # # # : 0 : IF (pred ? Select(*pred, cond, false) : cond) { // ignore entries for which predication predicate is not fulfilled
# # # # #
# ]
3609 [ # # # # ]: 0 : IF (not *first_iteration) {
3610 : 0 : store_locals_to_globals();
3611 : 0 : emit_group_and_resume_pipeline();
3612 : 0 : reset_aggs.attach_to_current();
3613 : 0 : };
3614 : 0 : update_keys.attach_to_current();
3615 : 0 : *first_iteration = false;
3616 : 0 : };
3617 : :
3618 : : /*----- Emit code to update aggregates. -----*/
3619 [ # # ]: 0 : update_aggs.attach_to_current();
3620 [ # # ]: 0 : update_avg_aggs.attach_to_current(); // after others to ensure that running count is incremented before
3621 : 0 : },
3622 [ # # # # ]: 0 : /* teardown= */ teardown_t::Make_Without_Parent([&](){
3623 : 0 : store_locals_to_globals();
3624 : :
3625 : : /*----- Destroy created aggregate values and their backups. -----*/
3626 [ # # ]: 0 : for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
3627 : 0 : agg_values[idx].~agg_t();
3628 : 0 : agg_value_backups[idx].~agg_backup_t();
3629 : 0 : }
3630 : :
3631 : 0 : M_insist(bool(first_iteration));
3632 : 0 : first_iteration_backup = *first_iteration;
3633 : 0 : first_iteration.reset();
3634 : 0 : })
3635 : : );
3636 : :
3637 : : /*----- If input was not empty, emit last group tuple in the current environment and resume the pipeline. -----*/
3638 [ # # # # ]: 0 : IF (not first_iteration_backup) {
3639 : 0 : emit_group_and_resume_pipeline();
3640 : 0 : };
3641 : :
3642 : : /*----- Delayed definition of function to emit group and resume pipeline (since result environment is needed). ---*/
3643 [ # # ]: 0 : auto fn = emit_group_and_resume_pipeline.make_function(); // outside BLOCK_OPEN-macro to register as current function
3644 [ # # # # ]: 0 : BLOCK_OPEN(fn.body()) {
3645 [ # # # # ]: 0 : auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
3646 [ # # # # ]: 0 : auto &env = CodeGenContext::Get().env();
3647 : :
3648 : : /*----- Emit setup code *before* possibly introducing temporary boolean variables to not overwrite them. -----*/
3649 [ # # ]: 0 : setup();
3650 : :
3651 : : /*----- Add computed group tuple to current environment. ----*/
3652 [ # # # # : 0 : for (auto &e : M.grouping.schema().deduplicate()) {
# # # # #
# ]
3653 : : try {
3654 [ # # ]: 0 : key_schema.find(e.id);
3655 [ # # ]: 0 : } catch (invalid_argument&) {
3656 : : continue; // skip duplicated keys since they must not be used afterwards
3657 [ # # ]: 0 : }
3658 : :
3659 [ # # # # ]: 0 : if (auto it = avg_aggregates.find(e.id);
3660 [ # # ]: 0 : it != avg_aggregates.end() and not it->second.compute_running_avg)
3661 : : { // AVG aggregates which is not yet computed, divide computed sum with computed count
3662 : 0 : auto &avg_info = it->second;
3663 [ # # ]: 0 : auto sum = results.get(avg_info.sum);
3664 [ # # # # : 0 : auto count = results.get<_I64x1>(avg_info.running_count).insist_not_null().to<double>();
# # ]
3665 [ # # # # ]: 0 : auto avg = convert<_Doublex1>(sum) / count;
3666 [ # # # # ]: 0 : if (avg.can_be_null()) {
3667 [ # # ]: 0 : _Var<Doublex1> var(avg); // introduce variable s.t. uses only load from it
3668 [ # # # # : 0 : env.add(e.id, var);
# # ]
3669 : 0 : } else {
3670 : : /* introduce variable w/o NULL bit s.t. uses only load from it */
3671 [ # # # # ]: 0 : Var<Doublex1> var(avg.insist_not_null());
3672 [ # # # # : 0 : env.add(e.id, _Doublex1(var));
# # # # ]
3673 : 0 : }
3674 : 0 : } else { // part of key or already computed aggregate
3675 [ # # ]: 0 : std::visit(overloaded {
3676 : 0 : [&]<typename T>(Expr<T> value) -> void {
3677 [ # # # # : 0 : if (value.can_be_null()) {
# # # # #
# # # #
# ]
3678 : 0 : Var<Expr<T>> var(value); // introduce variable s.t. uses only load from it
3679 [ # # # # : 0 : env.add(e.id, var);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
3680 : 0 : } else {
3681 : : /* introduce variable w/o NULL bit s.t. uses only load from it */
3682 [ # # # # : 0 : Var<PrimitiveExpr<T>> var(value.insist_not_null());
# # # # #
# # # #
# ]
3683 [ # # # # : 0 : env.add(e.id, Expr<T>(var));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
3684 : 0 : }
3685 : 0 : },
3686 : 0 : [&](NChar value) -> void {
3687 [ # # ]: 0 : Var<Ptr<Charx1>> var(value.val()); // introduce variable s.t. uses only load from it
3688 [ # # # # : 0 : env.add(e.id, NChar(var, value.can_be_null(), value.length(),
# # # # #
# ]
3689 : 0 : value.guarantees_terminating_nul()));
3690 : 0 : },
3691 : 0 : [](auto) -> void { M_unreachable("SIMDfication currently not supported"); },
3692 : 0 : [](std::monostate) -> void { M_unreachable("invalid reference"); },
3693 [ # # ]: 0 : }, results.get(e.id)); // do not extract to be able to access for not-yet-computed AVG aggregates
3694 : : }
3695 : : }
3696 : :
3697 : : /*----- Resume pipeline. -----*/
3698 [ # # ]: 0 : pipeline();
3699 : :
3700 : : /*----- Emit teardown code. -----*/
3701 [ # # ]: 0 : teardown();
3702 : 0 : }
3703 : 0 : }
3704 : :
3705 : :
3706 : : /*======================================================================================================================
3707 : : * Aggregation
3708 : : *====================================================================================================================*/
3709 : :
3710 : 0 : ConditionSet Aggregation::pre_condition(std::size_t child_idx, const std::tuple<const AggregationOperator*>&)
3711 : : {
3712 : 0 : M_insist(child_idx == 0);
3713 : :
3714 : 0 : ConditionSet pre_cond;
3715 : :
3716 : 0 : return pre_cond;
3717 [ # # ]: 0 : }
3718 : :
3719 : 0 : ConditionSet Aggregation::post_condition(const Match<Aggregation> &M)
3720 : : {
3721 : 0 : ConditionSet post_cond;
3722 : :
3723 : : /*----- Aggregation does not introduce predication. -----*/
3724 [ # # # # ]: 0 : post_cond.add_condition(Predicated(false));
3725 : :
3726 : : /*----- Aggregation does implicitly sort the data since only one tuple is produced. -----*/
3727 : 0 : Sortedness::order_t orders;
3728 [ # # # # : 0 : for (auto &e : M.aggregation.schema().deduplicate())
# # # # #
# ]
3729 [ # # # # ]: 0 : orders.add(e.id, Sortedness::O_UNDEF);
3730 [ # # # # ]: 0 : post_cond.add_condition(Sortedness(std::move(orders)));
3731 : :
3732 : : /*----- Aggregation does not introduce SIMD since only one tuple is produced. -----*/
3733 [ # # # # ]: 0 : post_cond.add_condition(NoSIMD());
3734 : :
3735 : 0 : return post_cond;
3736 [ # # ]: 0 : }
3737 : :
3738 : 0 : void Aggregation::execute(const Match<Aggregation> &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
3739 : : {
3740 : 0 : Environment results; ///< stores result tuple
3741 : : ///> code to construct aggregates from SIMD vectors; must be emitted *after* the child pipeline is executed
3742 : 0 : std::vector<std::function<void(void)>> finalize_aggregates;
3743 : :
3744 : : /*----- Compute information about aggregates, especially about AVG aggregates. -----*/
3745 [ # # # # : 0 : auto p = compute_aggregate_info(M.aggregation.aggregates(), M.aggregation.schema());
# # ]
3746 : 0 : const auto &aggregates = p.first;
3747 : 0 : const auto &avg_aggregates = p.second;
3748 : :
3749 : : /*----- Set minimal number of SIMD lanes preferred to get fully utilized SIMD vectors for the aggregate args. ----*/
3750 : 0 : uint64_t min_size_in_bytes = 16;
3751 [ # # # # ]: 0 : for (auto &fn : M.aggregation.aggregates()) {
3752 [ # # ]: 0 : for (auto &e : fn.get().args) {
3753 [ # # # # ]: 0 : visit(overloaded {
3754 : 0 : [](const m::ast::ErrorExpr&) -> void { M_unreachable("no errors at this stage"); },
3755 : 0 : [](const m::ast::Designator&) -> void { /* nothing to be done */ },
3756 : 0 : [](const m::ast::Constant&) -> void { /* nothing to be done */ },
3757 : 0 : [](const m::ast::QueryExpr&) -> void { /* nothing to be done */ },
3758 : 0 : [&min_size_in_bytes](const m::ast::FnApplicationExpr &fn) -> void {
3759 : 0 : M_insist(not fn.get_function().is_aggregate(), "aggregate arguments must not be aggregates");
3760 : 0 : min_size_in_bytes = std::min(min_size_in_bytes, (fn.type()->size() + 7) / 8);
3761 [ # # ]: 0 : if (min_size_in_bytes == 1)
3762 : 0 : throw visit_stop_recursion(); // abort recursion
3763 : 0 : },
3764 : 0 : [&min_size_in_bytes](auto &e) -> void { // i.e. for unary and binary expressions
3765 : 0 : min_size_in_bytes = std::min(min_size_in_bytes, (e.type()->size() + 7) / 8);
3766 [ # # # # ]: 0 : if (min_size_in_bytes == 1)
3767 : 0 : throw visit_stop_recursion(); // abort recursion
3768 : 0 : }
3769 : 0 : }, *e, m::tag<m::ast::ConstPreOrderExprVisitor>());
3770 : : }
3771 : : }
3772 [ # # # # ]: 0 : CodeGenContext::Get().update_num_simd_lanes_preferred(16 / min_size_in_bytes); // set own preference
3773 : :
3774 : : /*----- Set minimal number of SIMD lanes preferred to be able to compute running averages. ----*/
3775 [ # # # # ]: 0 : if (std::any_of(avg_aggregates.begin(), avg_aggregates.end(), [](auto &i){ return i.second.compute_running_avg; }))
3776 [ # # # # ]: 0 : CodeGenContext::Get().update_num_simd_lanes_preferred(4); // set own preference
3777 : :
3778 : : /*----- Create child function. -----*/
3779 [ # # # # : 0 : FUNCTION(aggregation_child_pipeline, void(void)) // create function for pipeline
# # # # ]
3780 : : {
3781 [ # # # # ]: 0 : auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
3782 : :
3783 : : #ifndef NDEBUG
3784 : : std::size_t num_simd_lanes; ///< to insist that setup, pipeline, and teardown callbacks use the same value
3785 : : #endif
3786 : : void *_agg_values; ///< *local* values of the computed aggregates
3787 : : void *_agg_value_backups; ///< *global* value backups of the computed aggregates
3788 : :
3789 [ # # ]: 0 : M.child->execute(
3790 [ # # # # ]: 0 : /* setup= */ setup_t::Make_Without_Parent([&]() {
3791 : 0 : auto execute_setup = [&]<std::size_t L>() {
3792 : : #ifndef NDEBUG
3793 : 0 : num_simd_lanes = L;
3794 : : #endif
3795 : :
3796 : : /*----- Initialize aggregates helper structures. -----*/
3797 : : using agg_t = agg_t_<false, L>;
3798 : : using agg_backup_t = agg_t_<true, L>;
3799 [ # # # # : 0 : auto agg_values = new agg_t[aggregates.size()];
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
3800 [ # # # # : 0 : auto agg_value_backups = new agg_backup_t[aggregates.size()];
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
3801 : :
3802 : : /*----- Store aggregates helper structures for pipeline and teardown callbacks. -----*/
3803 : 0 : _agg_values = static_cast<void*>(agg_values);
3804 : 0 : _agg_value_backups = static_cast<void*>(agg_value_backups);
3805 : :
3806 : : /*----- Initialize aggregates and their backups. -----*/
3807 [ # # # # : 0 : for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
# # # # #
# # # ]
3808 : 0 : auto &info = aggregates[idx];
3809 : :
3810 : 0 : bool is_min = false; ///< flag to indicate whether aggregate function is MIN
3811 [ # # # # : 0 : switch (info.fnid) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
3812 : : default:
3813 : 0 : M_unreachable("unsupported aggregate function");
3814 : : case m::Function::FN_MIN:
3815 : 0 : is_min = true; // set flag and delegate to MAX case
3816 : : case m::Function::FN_MAX: {
3817 : 0 : auto min_max = [&]<typename T>() {
3818 : 0 : auto neutral = is_min ? std::numeric_limits<T>::max()
3819 : 0 : : std::numeric_limits<T>::lowest();
3820 : :
3821 : 0 : Var<PrimitiveExpr<T, L>> min_max;
3822 [ # # # # : 0 : Global<PrimitiveExpr<T, L>> min_max_backup(
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
3823 : : neutral // initialize with neutral element +inf or -inf
3824 : : );
3825 [ # # # # : 0 : Var<Bool<L>> is_null;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
3826 [ # # # # : 0 : Global<Bool<L>> is_null_backup(true); // MIN/MAX is initially NULL
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
3827 : :
3828 : : /*----- Set local aggregate variables to global backups. -----*/
3829 [ # # # # : 0 : min_max = min_max_backup;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
3830 [ # # # # : 0 : is_null = is_null_backup;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
3831 : :
3832 : : /*----- Add global aggregate to result env. to access it in other function. -----*/
3833 : : if constexpr (L == 1) { // scalar
3834 [ # # # # : 0 : PrimitiveExpr<T> value = min_max_backup;
# # # # #
# # # ]
3835 [ # # # # : 0 : Boolx1 is_null = is_null_backup;
# # # # #
# # # ]
3836 [ # # # # : 0 : results.add(info.entry.id, Select(is_null, Expr<T>::Null(), value));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
3837 : 0 : } else { // vectorial
3838 : : /* Create lambda which emits the computation of the final *scalar* aggregate.
3839 : : * This can then be called in the pipeline function starting at the aggregation
3840 : : * operator s.t. the emitted variable is a local of the correct function.
3841 : : * Do not access the global variables inside the lambda using closure by
3842 : : * reference since they are already destroyed when the lambda will be called.
3843 : : * Instead, copy their values into the lambda. However, since DSL expressions
3844 : : * are not const-copy-constructible, we have to allocate them on the heap and
3845 : : * destroy them manually inside the lambda. */
3846 [ # # # # : 0 : auto simd_min_max = new PrimitiveExpr<T, L>(min_max_backup.val());
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
3847 [ # # # # : 0 : auto simd_is_null = new Bool<L>(is_null_backup.val());
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
3848 [ # # # # : 0 : finalize_aggregates.emplace_back([&, is_min, simd_min_max, simd_is_null]() {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
3849 [ # # # # : 0 : PrimitiveExpr<T> value = [&]<std::size_t... Is>(std::index_sequence<Is...>) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
3850 [ # # # # : 0 : Var<PrimitiveExpr<T>> res(simd_min_max->clone().template extract<0>());
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
3851 : 0 : auto update = [&]<std::size_t I>(){
3852 : : if constexpr (requires (PrimitiveExpr<T> v) { min(v, v); max(v, v); }) {
3853 [ # # # # : 0 : res = is_min ? min(res, simd_min_max->clone().template extract<I>())
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
3854 [ # # # # : 0 : : max(res, simd_min_max->clone().template extract<I>());
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
3855 : : } else {
3856 [ # # # # : 0 : const Var<PrimitiveExpr<T>> extracted(
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
3857 [ # # # # : 0 : simd_min_max->clone().template extract<I>()
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
3858 : : ); // due to multiple uses
3859 [ # # # # : 0 : auto cmp = is_min ? extracted < res : extracted > res;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
3860 : : #if 1
3861 [ # # # # : 0 : res = Select(cmp, extracted, res);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
3862 : : #else
3863 : : IF (cmp) {
3864 : : res = extracted;
3865 : : };
3866 : : #endif
3867 : 0 : }
3868 : 0 : };
3869 [ # # # # : 0 : (update.template operator()<Is + 1>(), ...);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
3870 [ # # # # : 0 : return res;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
3871 : 0 : }(std::make_index_sequence<L - 1>{});
3872 [ # # # # : 0 : simd_min_max->discard(); // since it was always cloned
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
3873 [ # # # # : 0 : Boolx1 is_null = simd_is_null->all_true();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
3874 [ # # # # : 0 : results.add(info.entry.id, Select(is_null, Expr<T>::Null(), value));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
3875 [ # # # # : 0 : delete simd_min_max; // destroy heap-allocated variable
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
3876 [ # # # # : 0 : delete simd_is_null; // destroy heap-allocated variable
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
3877 : 0 : });
3878 : : }
3879 : :
3880 : : /*----- Move aggregate variables to access them later. ----*/
3881 [ # # # # : 0 : new (&agg_values[idx]) agg_t(std::make_pair(
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
3882 : : std::move(min_max), std::move(is_null))
3883 : : );
3884 [ # # # # : 0 : new (&agg_value_backups[idx]) agg_backup_t(std::make_pair(
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
3885 : : std::move(min_max_backup), std::move(is_null_backup)
3886 : : ));
3887 : 0 : };
3888 : 0 : auto &n = as<const Numeric>(*info.entry.type);
3889 [ # # # # : 0 : switch (n.kind) {
# # # # #
# # # # #
# # # # ]
3890 : : case Numeric::N_Int:
3891 : : case Numeric::N_Decimal:
3892 [ # # # # : 0 : switch (n.size()) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
3893 : 0 : default: M_unreachable("invalid size");
3894 : 0 : case 8: min_max.template operator()<int8_t >(); break;
3895 : 0 : case 16: min_max.template operator()<int16_t>(); break;
3896 : 0 : case 32: min_max.template operator()<int32_t>(); break;
3897 : 0 : case 64: min_max.template operator()<int64_t>(); break;
3898 : : }
3899 : 0 : break;
3900 : : case Numeric::N_Float:
3901 [ # # # # : 0 : if (n.size() <= 32)
# # # # #
# # # ]
3902 : 0 : min_max.template operator()<float>();
3903 : : else
3904 : 0 : min_max.template operator()<double>();
3905 : 0 : }
3906 : 0 : break;
3907 : : }
3908 : : case m::Function::FN_AVG:
3909 : 0 : break; // skip here and handle later
3910 : : case m::Function::FN_SUM: {
3911 : 0 : auto sum = [&]<typename T>() {
3912 : 0 : Var<PrimitiveExpr<T, L>> sum;
3913 [ # # # # : 0 : Global<PrimitiveExpr<T, L>> sum_backup(T(0)); // initialize with neutral element 0
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
3914 [ # # # # : 0 : Var<Bool<L>> is_null;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
3915 [ # # # # : 0 : Global<Bool<L>> is_null_backup(true); // SUM is initially NULL
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
3916 : :
3917 : : /*----- Set local aggregate variables to global backups. -----*/
3918 [ # # # # : 0 : sum = sum_backup;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
3919 [ # # # # : 0 : is_null = is_null_backup;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
3920 : :
3921 : : /*----- Add global aggregate to result env. to access it in other function. -----*/
3922 : : if constexpr (L == 1) { // scalar
3923 [ # # # # : 0 : PrimitiveExpr<T> value = sum_backup;
# # # # #
# # # ]
3924 [ # # # # : 0 : Boolx1 is_null = is_null_backup;
# # # # #
# # # ]
3925 [ # # # # : 0 : results.add(info.entry.id, Select(is_null, Expr<T>::Null(), value));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
3926 : 0 : } else { // vectorial
3927 [ # # # # : 0 : PrimitiveExpr<T> value = [&]<std::size_t... Is>(std::index_sequence<Is...>) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
3928 [ # # # # : 0 : return (sum_backup.template extract<Is>() + ...);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
3929 : 0 : }(std::make_index_sequence<L>{});
3930 [ # # # # : 0 : Boolx1 is_null = is_null_backup.all_true();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
3931 [ # # # # : 0 : results.add(info.entry.id, Select(is_null, Expr<T>::Null(), value));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
3932 : 0 : }
3933 : :
3934 : : /*----- Move aggregate variables to access them later. ----*/
3935 [ # # # # : 0 : new (&agg_values[idx]) agg_t(std::make_pair(std::move(sum), std::move(is_null)));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
3936 [ # # # # : 0 : new (&agg_value_backups[idx]) agg_backup_t(std::make_pair(
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
3937 : : std::move(sum_backup), std::move(is_null_backup)
3938 : : ));
3939 : 0 : };
3940 : 0 : auto &n = as<const Numeric>(*info.entry.type);
3941 [ # # # # : 0 : switch (n.kind) {
# # # # #
# # # # #
# # # # ]
3942 : : case Numeric::N_Int:
3943 : : case Numeric::N_Decimal:
3944 [ # # # # : 0 : switch (n.size()) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
3945 : 0 : default: M_unreachable("invalid size");
3946 : 0 : case 8: sum.template operator()<int8_t >(); break;
3947 : 0 : case 16: sum.template operator()<int16_t>(); break;
3948 : 0 : case 32: sum.template operator()<int32_t>(); break;
3949 : 0 : case 64: sum.template operator()<int64_t>(); break;
3950 : : }
3951 : 0 : break;
3952 : : case Numeric::N_Float:
3953 [ # # # # : 0 : if (n.size() <= 32)
# # # # #
# # # ]
3954 : 0 : sum.template operator()<float>();
3955 : : else
3956 : 0 : sum.template operator()<double>();
3957 : 0 : }
3958 : 0 : break;
3959 : : }
3960 : : case m::Function::FN_COUNT: {
3961 : 0 : Var<I64<L>> count;
3962 [ # # # # : 0 : Global<I64<L>> count_backup(0); // initialize with neutral element 0
# # # # #
# # # ]
3963 : : /* no `is_null` variables needed since COUNT will not be NULL */
3964 : :
3965 : : /*----- Set local aggregate variable to global backup. -----*/
3966 [ # # # # : 0 : count = count_backup;
# # # # #
# # # ]
3967 : :
3968 : : /*----- Add global aggregate to result env. to access it in other function. -----*/
3969 : : if constexpr (L == 1) { // scalar
3970 [ # # ]: 0 : I64x1 value = count_backup;
3971 [ # # # # : 0 : results.add(info.entry.id, value);
# # ]
3972 : 0 : } else { // vectorial
3973 [ # # # # : 0 : I64x1 value = [&]<std::size_t... Is>(std::index_sequence<Is...>) {
# # # # #
# ]
3974 [ # # # # : 0 : return (count_backup.template extract<Is>() + ...);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
3975 : 0 : }(std::make_index_sequence<L>{});
3976 [ # # # # : 0 : results.add(info.entry.id, value);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
3977 : 0 : }
3978 : :
3979 : : /*----- Move aggregate variables to access them later. ----*/
3980 [ # # # # : 0 : new (&agg_values[idx]) agg_t(std::move(count));
# # # # #
# # # ]
3981 [ # # # # : 0 : new (&agg_value_backups[idx]) agg_backup_t(std::move(count_backup));
# # # # #
# # # ]
3982 : :
3983 : : break;
3984 : 0 : }
3985 : : }
3986 : 0 : }
3987 : :
3988 : : /*----- Initialize AVG aggregates after others to ensure that running count is initialized before. */
3989 [ # # # # : 0 : for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
# # # # #
# # # ]
3990 : 0 : auto &info = aggregates[idx];
3991 : :
3992 [ # # # # : 0 : if (info.fnid == m::Function::FN_AVG) {
# # # # #
# # # ]
3993 : 0 : Var<Double<L>> avg;
3994 [ # # # # : 0 : Global<Double<L>> avg_backup(0.0); // initialize with neutral element 0
# # # # #
# # # ]
3995 [ # # # # : 0 : Var<Bool<L>> is_null;
# # # # #
# # # ]
3996 [ # # # # : 0 : Global<Bool<L>> is_null_backup(true); // AVG is initially NULL
# # # # #
# # # ]
3997 : :
3998 : : /*----- Set local aggregate variables to global backups. -----*/
3999 [ # # # # : 0 : avg = avg_backup;
# # # # #
# # # ]
4000 [ # # # # : 0 : is_null = is_null_backup;
# # # # #
# # # ]
4001 : :
4002 : : /*----- Add global aggregate to result env. to access it in other function. -----*/
4003 : : if constexpr (L == 1) { // scalar
4004 [ # # ]: 0 : Doublex1 value = avg_backup;
4005 [ # # ]: 0 : Boolx1 is_null = is_null_backup;
4006 [ # # # # : 0 : results.add(info.entry.id, Select(is_null, _Doublex1::Null(), value));
# # # # ]
4007 : 0 : } else { // vectorial
4008 : : /* Create lambda which emits the computation of the final *scalar* aggregate.
4009 : : * This can then be called in the pipeline function starting at the aggregation
4010 : : * operator s.t. the emitted variable is a local of the correct function.
4011 : : * Do not access the global variables inside the lambda using closure by
4012 : : * reference since they are already destroyed when the lambda will be called.
4013 : : * Instead, copy their values into the lambda. However, since DSL expressions
4014 : : * are not const-copy-constructible, we have to allocate them on the heap and
4015 : : * destroy them manually inside the lambda. */
4016 [ # # # # : 0 : auto simd_avg = new Double<L>(avg_backup.val());
# # # # #
# # # # #
# # # # #
# ]
4017 [ # # # # : 0 : auto simd_is_null = new Bool<L>(is_null_backup.val());
# # # # #
# # # # #
# # # # #
# ]
4018 [ # # # # : 0 : auto simd_running_count = new I64<L>([&](){
# # # # #
# # # # #
# # # # #
# ]
4019 : 0 : auto it = avg_aggregates.find(info.entry.id);
4020 : 0 : M_insist(it != avg_aggregates.end());
4021 : 0 : const auto &avg_info = it->second;
4022 : 0 : M_insist(avg_info.compute_running_avg,
4023 : : "AVG aggregate may only occur for running average computations");
4024 : :
4025 : 0 : auto running_count_idx = std::distance(
4026 : 0 : aggregates.cbegin(),
4027 : 0 : std::find_if(
4028 : 0 : aggregates.cbegin(), aggregates.cend(), [&avg_info](const auto &info){
4029 : 0 : return info.entry.id == avg_info.running_count;
4030 : : })
4031 : : );
4032 [ # # # # : 0 : M_insist(0 <= running_count_idx and running_count_idx < aggregates.size());
# # # # #
# ]
4033 : :
4034 : 0 : auto &running_count =
4035 : 0 : *M_notnull(std::get_if<Global<I64<L>>>(&agg_value_backups[running_count_idx]));
4036 : 0 : return running_count.val();
4037 : : }());
4038 [ # # # # : 0 : finalize_aggregates.emplace_back([&, simd_avg, simd_is_null, simd_running_count]() {
# # # # #
# ]
4039 : 0 : Doublex1 value = [&]<std::size_t... Is>(std::index_sequence<Is...>) {
4040 [ # # # # : 0 : I64x1 count = (simd_running_count->clone().template extract<Is>() + ...);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
4041 [ # # # # : 0 : const Var<Double<L>> simd_sum([&](){
# # # # #
# # # # #
# # # # #
# ]
4042 : : if constexpr (L != 2) {
4043 [ # # # # : 0 : return *simd_avg * simd_running_count->template to<double>();
# # # # ]
4044 : : } else {
4045 : 0 : M_unreachable("conversion from `I64<2>` to `Double<2>` not supported");
4046 : : return Double<L>(0.0); // this line is never reached; return dummy value
4047 : : }
4048 : 0 : }());
4049 [ # # # # : 0 : return (simd_sum.template extract<Is>() + ...) / count.to<double>();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
4050 : 0 : }(std::make_index_sequence<L>{});
4051 [ # # # # : 0 : Boolx1 is_null = simd_is_null->all_true();
# # # # #
# ]
4052 [ # # # # : 0 : results.add(info.entry.id, Select(is_null, _Doublex1::Null(), value));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
4053 [ # # # # : 0 : delete simd_avg; // destroy heap-allocated variable
# # # # #
# ]
4054 [ # # # # : 0 : delete simd_is_null; // destroy heap-allocated variable
# # # # #
# ]
4055 [ # # # # : 0 : delete simd_running_count; // destroy heap-allocated variable
# # # # #
# ]
4056 : 0 : });
4057 : : }
4058 : :
4059 : : /*----- Move aggregate variables to access them later. ----*/
4060 [ # # # # : 0 : new (&agg_values[idx]) agg_t(std::make_pair(std::move(avg), std::move(is_null)));
# # # # #
# # # # #
# # # # #
# # # #
# ]
4061 [ # # # # : 0 : new (&agg_value_backups[idx]) agg_backup_t(std::make_pair(
# # # # #
# # # # #
# # # # #
# # # #
# ]
4062 : : std::move(avg_backup), std::move(is_null_backup)
4063 : : ));
4064 : 0 : }
4065 : 0 : }
4066 : 0 : };
4067 [ # # # # : 0 : switch (CodeGenContext::Get().num_simd_lanes()) {
# # # ]
4068 : 0 : default: M_unreachable("unsupported number of SIMD lanes");
4069 : 0 : case 1: execute_setup.operator()<1>(); break;
4070 : 0 : case 2: execute_setup.operator()<2>(); break;
4071 : 0 : case 4: execute_setup.operator()<4>(); break;
4072 : 0 : case 8: execute_setup.operator()<8>(); break;
4073 : 0 : case 16: execute_setup.operator()<16>(); break;
4074 : 0 : case 32: execute_setup.operator()<32>(); break;
4075 : : }
4076 : 0 : }),
4077 [ # # ]: 0 : /* pipeline= */ [&](){
4078 : 0 : auto execute_pipeline = [&]<std::size_t L>(){
4079 : : #ifndef NDEBUG
4080 : 0 : M_insist(num_simd_lanes == L,
4081 : : "number of SIMD lanes in pipeline callback must match the one in setup callback");
4082 : : #endif
4083 : :
4084 : : /*----- Get aggregates helper structures. -----*/
4085 : : using agg_t = agg_t_<false, L>;
4086 : : using agg_backup_t = agg_t_<true, L>;
4087 : 0 : auto agg_values = static_cast<agg_t*>(_agg_values);
4088 : 0 : auto agg_value_backups = static_cast<agg_backup_t*>(_agg_value_backups);
4089 : :
4090 : 0 : auto &env = CodeGenContext::Get().env();
4091 : :
4092 : : /*----- If predication is used, introduce pred. var. and update it before computing aggregates. --*/
4093 : 0 : std::optional<Var<Bool<L>>> pred;
4094 [ # # # # : 0 : if (env.predicated()) {
# # # # #
# # # ]
4095 : : if constexpr (sql_boolean_type<_Bool<L>>)
4096 [ # # # # : 0 : pred = env.extract_predicate<_Bool<L>>().is_true_and_not_null();
# # # # #
# # # # #
# # # # ]
4097 : : else
4098 [ # # # # : 0 : M_unreachable("invalid number of SIMD lanes");
# # ]
4099 : 0 : }
4100 : :
4101 : : /*----- Compute aggregates (except AVG). -----*/
4102 [ # # # # : 0 : for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
# # # # #
# # # ]
4103 : 0 : auto &info = aggregates[idx];
4104 : :
4105 : 0 : bool is_min = false; ///< flag to indicate whether aggregate function is MIN
4106 [ # # # # : 0 : switch (info.fnid) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
4107 : : default:
4108 [ # # # # : 0 : M_unreachable("unsupported aggregate function");
# # # # #
# # # ]
4109 : : case m::Function::FN_MIN:
4110 : 0 : is_min = true; // set flag and delegate to MAX case
4111 : : case m::Function::FN_MAX: {
4112 [ # # # # : 0 : M_insist(info.args.size() == 1,
# # # # #
# # # ]
4113 : : "MIN and MAX aggregate functions expect exactly one argument");
4114 : 0 : const auto &arg = *info.args[0];
4115 : : auto min_max = overloaded{
4116 : 0 : [&]<typename T>() requires sql_type<Expr<T, L>> {
4117 : 0 : auto &[min_max, is_null] = *M_notnull((
4118 : : std::get_if<
4119 : : std::pair<Var<PrimitiveExpr<T, L>>, Var<Bool<L>>>
4120 : : >(&agg_values[idx])
4121 : : ));
4122 : :
4123 : 0 : auto _arg = env.compile(arg);
4124 [ # # # # : 0 : Expr<T, L> _new_val = convert<Expr<T, L>>(_arg);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
4125 [ # # # # : 0 : if (_new_val.can_be_null()) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
4126 : : M_insist_no_ternary_logic();
4127 : : auto _new_val_pred =
4128 [ # # # # : 0 : pred ? Select(*pred, _new_val, Expr<T, L>::Null()) : _new_val;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
4129 [ # # # # : 0 : auto [new_val_, new_val_is_null_] = _new_val_pred.split();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
4130 [ # # # # : 0 : const Var<Bool<L>> new_val_is_null(new_val_is_null_); // due to multiple uses
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
4131 : :
4132 : : if constexpr (requires (PrimitiveExpr<T, L> v) { min(v, v); max(v, v); }) {
4133 [ # # # # : 0 : min_max = Select(new_val_is_null,
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
4134 : : min_max, // ignore NULL
4135 [ # # # # : 0 : is_min ? min(min_max, new_val_) // update old min with new value
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
4136 [ # # # # : 0 : : max(min_max, new_val_)); // update old max with new value
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
4137 : : } else {
4138 [ # # # # : 0 : const Var<PrimitiveExpr<T, L>> new_val(new_val_); // due to multiple uses
# # # # #
# # # # #
# # # # ]
4139 [ # # # # : 0 : auto cmp = is_min ? new_val < min_max : new_val > min_max;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
4140 : : #if 1
4141 [ # # # # : 0 : min_max = Select(new_val_is_null,
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
4142 : : min_max, // ignore NULL
4143 [ # # # # : 0 : Select(cmp,
# # # # #
# # # # #
# # # # ]
4144 : : new_val, // update to new value
4145 : : min_max)); // do not update
4146 : : #else
4147 : : IF (not new_val_is_null and cmp) {
4148 : : min_max = new_val;
4149 : : };
4150 : : #endif
4151 : 0 : }
4152 [ # # # # : 0 : is_null = is_null and new_val_is_null; // MIN/MAX is NULL iff all values are NULL
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
4153 : 0 : } else {
4154 : 0 : auto neutral = is_min ? std::numeric_limits<T>::max()
4155 : 0 : : std::numeric_limits<T>::lowest();
4156 : : auto _new_val_pred =
4157 [ # # # # : 0 : pred ? Select(*pred, _new_val, PrimitiveExpr<T, L>(neutral)) : _new_val;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
4158 [ # # # # : 0 : auto new_val_ = _new_val_pred.insist_not_null();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
4159 : : if constexpr (requires (PrimitiveExpr<T, L> v) { min(v, v); max(v, v); }) {
4160 [ # # # # : 0 : min_max = is_min ? min(min_max, new_val_) // update old min with new value
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
4161 [ # # # # : 0 : : max(min_max, new_val_); // update old max with new value
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
4162 : : } else {
4163 [ # # # # : 0 : const Var<PrimitiveExpr<T, L>> new_val(new_val_); // due to multiple uses
# # # # #
# # # # #
# # # # ]
4164 [ # # # # : 0 : auto cmp = is_min ? new_val < min_max : new_val > min_max;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
4165 : : #if 1
4166 [ # # # # : 0 : min_max = Select(cmp,
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
4167 : : new_val, // update to new value
4168 : : min_max); // do not update
4169 : : #else
4170 : : IF (cmp) {
4171 : : min_max = new_val;
4172 : : };
4173 : : #endif
4174 : 0 : }
4175 [ # # # # : 0 : is_null.set_false(); // at least one non-NULL value is consumed
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
4176 : 0 : }
4177 : 0 : },
4178 : 0 : []<typename>() { M_unreachable("invalid type for given number of SIMD lanes"); }
4179 : : };
4180 [ # # # # : 0 : auto &n = as<const Numeric>(*info.entry.type);
# # # # #
# # # ]
4181 [ # # # # : 0 : switch (n.kind) {
# # # # #
# # # # #
# # # # ]
4182 : : case Numeric::N_Int:
4183 : : case Numeric::N_Decimal:
4184 [ # # # # : 0 : switch (n.size()) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
4185 [ # # # # : 0 : default: M_unreachable("invalid size");
# # # # #
# # # ]
4186 [ # # # # : 0 : case 8: min_max.template operator()<int8_t >(); break;
# # # # #
# # # ]
4187 [ # # # # : 0 : case 16: min_max.template operator()<int16_t>(); break;
# # # # #
# # # ]
4188 [ # # # # : 0 : case 32: min_max.template operator()<int32_t>(); break;
# # # # #
# # # ]
4189 [ # # # # : 0 : case 64: min_max.template operator()<int64_t>(); break;
# # # # #
# # # ]
4190 : : }
4191 : 0 : break;
4192 : : case Numeric::N_Float:
4193 [ # # # # : 0 : if (n.size() <= 32)
# # # # #
# # # # #
# # # # #
# # # #
# ]
4194 [ # # # # : 0 : min_max.template operator()<float>();
# # # # #
# # # ]
4195 : : else
4196 [ # # # # : 0 : min_max.template operator()<double>();
# # # # #
# # # ]
4197 : 0 : }
4198 : 0 : break;
4199 : : }
4200 : : case m::Function::FN_AVG:
4201 : 0 : break; // skip here and handle later
4202 : : case m::Function::FN_SUM: {
4203 [ # # # # : 0 : M_insist(info.args.size() == 1, "SUM aggregate function expects exactly one argument");
# # # # #
# # # ]
4204 : 0 : const auto &arg = *info.args[0];
4205 : :
4206 : : auto sum = overloaded{
4207 : 0 : [&]<typename T>() requires sql_type<Expr<T, L>> {
4208 : 0 : auto &[sum, is_null] = *M_notnull((
4209 : : std::get_if<
4210 : : std::pair<Var<PrimitiveExpr<T, L>>, Var<Bool<L>>>
4211 : : >(&agg_values[idx])
4212 : : ));
4213 : :
4214 : 0 : auto _arg = env.compile(arg);
4215 [ # # # # : 0 : Expr<T, L> _new_val = convert<Expr<T, L>>(_arg);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
4216 [ # # # # : 0 : if (_new_val.can_be_null()) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
4217 : : M_insist_no_ternary_logic();
4218 : : auto _new_val_pred =
4219 [ # # # # : 0 : pred ? Select(*pred, _new_val, Expr<T, L>::Null()) : _new_val;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
4220 [ # # # # : 0 : auto [new_val, new_val_is_null_] = _new_val_pred.split();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
4221 [ # # # # : 0 : const Var<Bool<L>> new_val_is_null(new_val_is_null_); // due to multiple uses
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
4222 : :
4223 [ # # # # : 0 : sum += Select(new_val_is_null,
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
4224 [ # # # # : 0 : PrimitiveExpr<T, L>(T(0)), // ignore NULL
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
4225 : : new_val); // add new value to old sum
4226 [ # # # # : 0 : is_null = is_null and new_val_is_null; // SUM is NULL iff all values are NULL
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
4227 : 0 : } else {
4228 : : auto _new_val_pred =
4229 [ # # # # : 0 : pred ? Select(*pred, _new_val, PrimitiveExpr<T, L>(T(0))) : _new_val;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
4230 [ # # # # : 0 : sum += _new_val_pred.insist_not_null(); // add new value to old sum
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
4231 [ # # # # : 0 : is_null.set_false(); // at least one non-NULL value is consumed
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
4232 : 0 : }
4233 : 0 : },
4234 : 0 : []<typename>() { M_unreachable("invalid type for given number of SIMD lanes"); }
4235 : : };
4236 [ # # # # : 0 : auto &n = as<const Numeric>(*info.entry.type);
# # # # #
# # # ]
4237 [ # # # # : 0 : switch (n.kind) {
# # # # #
# # # # #
# # # # ]
4238 : : case Numeric::N_Int:
4239 : : case Numeric::N_Decimal:
4240 [ # # # # : 0 : switch (n.size()) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
4241 [ # # # # : 0 : default: M_unreachable("invalid size");
# # # # #
# # # ]
4242 [ # # # # : 0 : case 8: sum.template operator()<int8_t >(); break;
# # # # #
# # # ]
4243 [ # # # # : 0 : case 16: sum.template operator()<int16_t>(); break;
# # # # #
# # # ]
4244 [ # # # # : 0 : case 32: sum.template operator()<int32_t>(); break;
# # # # #
# # # ]
4245 [ # # # # : 0 : case 64: sum.template operator()<int64_t>(); break;
# # # # #
# # # ]
4246 : : }
4247 : 0 : break;
4248 : : case Numeric::N_Float:
4249 [ # # # # : 0 : if (n.size() <= 32)
# # # # #
# # # # #
# # # # #
# # # #
# ]
4250 [ # # # # : 0 : sum.template operator()<float>();
# # # # #
# # # ]
4251 : : else
4252 [ # # # # : 0 : sum.template operator()<double>();
# # # # #
# # # ]
4253 : 0 : }
4254 : 0 : break;
4255 : : }
4256 : : case m::Function::FN_COUNT: {
4257 [ # # # # : 0 : M_insist(info.args.size() <= 1, "COUNT aggregate function expects at most one argument");
# # # # #
# # # ]
4258 [ # # # # : 0 : M_insist(info.entry.type->is_integral() and info.entry.type->size() == 64);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
4259 : :
4260 [ # # # # : 0 : auto &count = *M_notnull(std::get_if<Var<I64<L>>>(&agg_values[idx]));
# # # # #
# # # ]
4261 : :
4262 [ # # # # : 0 : if (info.args.empty()) {
# # # # #
# # # ]
4263 [ # # # # : 0 : count += pred ? pred->template to<int64_t>() : I64<L>(1); // increment old count by 1 iff `pred` is true
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
4264 : 0 : } else {
4265 [ # # # # : 0 : auto _new_val = env.compile(*info.args[0]);
# # # # #
# # # ]
4266 [ # # # # : 0 : if (can_be_null(_new_val)) {
# # # # #
# # # # #
# # # # #
# # # #
# ]
4267 : : M_insist_no_ternary_logic();
4268 [ # # # # : 0 : I64<L> inc = pred ? (not_null<L>(_new_val) and *pred).template to<int64_t>()
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
4269 [ # # # # : 0 : : not_null<L>(_new_val).template to<int64_t>();
# # # # #
# # # # #
# # # # #
# # # #
# ]
4270 [ # # # # : 0 : count += inc; // increment old count by 1 iff new value is present and `pred` is true
# # # # #
# # # ]
4271 : 0 : } else {
4272 [ # # # # : 0 : discard(_new_val); // since it is not needed in this case
# # # # #
# # # ]
4273 [ # # # # : 0 : I64<L> inc = pred ? pred->template to<int64_t>() : I64<L>(1);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
4274 [ # # # # : 0 : count += inc; // increment old count by 1 iff new value is present and `pred` is true
# # # # #
# # # ]
4275 : 0 : }
4276 : 0 : }
4277 : 0 : break;
4278 : : }
4279 : : }
4280 : 0 : }
4281 : :
4282 : : /*----- Compute AVG aggregates after others to ensure that running count is incremented before. --*/
4283 [ # # # # : 0 : for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
# # # # #
# # # ]
4284 : 0 : auto &info = aggregates[idx];
4285 : :
4286 [ # # # # : 0 : if (info.fnid == m::Function::FN_AVG) {
# # # # #
# # # ]
4287 [ # # # # : 0 : M_insist(info.args.size() == 1, "AVG aggregate function expects exactly one argument");
# # # # #
# # # ]
4288 : 0 : const auto &arg = *info.args[0];
4289 [ # # # # : 0 : M_insist(info.entry.type->is_double());
# # # # #
# # # # #
# # # # #
# # # #
# ]
4290 : :
4291 [ # # # # : 0 : auto it = avg_aggregates.find(info.entry.id);
# # # # #
# # # ]
4292 [ # # # # : 0 : M_insist(it != avg_aggregates.end());
# # # # #
# # # ]
4293 : 0 : const auto &avg_info = it->second;
4294 [ # # # # : 0 : M_insist(avg_info.compute_running_avg,
# # # # #
# # # ]
4295 : : "AVG aggregate may only occur for running average computations");
4296 : :
4297 [ # # # # : 0 : auto &[avg, is_null] = *M_notnull((
# # # # #
# # # ]
4298 : : std::get_if<std::pair<Var<Double<L>>, Var<Bool<L>>>>(&agg_values[idx])
4299 : : ));
4300 : :
4301 : : /* Compute AVG as iterative mean as described in Knuth, The Art of Computer Programming
4302 : : * Vol 2, section 4.2.2. */
4303 [ # # # # : 0 : auto running_count_idx = std::distance(
# # # # #
# # # ]
4304 : 0 : aggregates.cbegin(),
4305 [ # # # # : 0 : std::find_if(aggregates.cbegin(), aggregates.cend(), [&avg_info](const auto &info){
# # # # #
# # # ]
4306 : 0 : return info.entry.id == avg_info.running_count;
4307 : : })
4308 : : );
4309 [ # # # # : 0 : M_insist(0 <= running_count_idx and running_count_idx < aggregates.size());
# # # # #
# # # # #
# # # # #
# # # #
# ]
4310 [ # # # # : 0 : Double<L> running_count = [&](){
# # # # #
# # # ]
4311 : 0 : auto &running_count =
4312 : 0 : *M_notnull(std::get_if<Var<I64<L>>>(&agg_values[running_count_idx]));
4313 : : if constexpr (L != 2) {
4314 : 0 : return running_count.template to<double>();
4315 : : } else {
4316 : 0 : M_unreachable("conversion from `I64<2>` to `Double<2>` not supported");
4317 : : return Double<L>(0.0); // this line is never reached; return dummy value
4318 : : }
4319 : : }();
4320 : :
4321 [ # # # # : 0 : auto _arg = env.compile(arg);
# # # # #
# # # ]
4322 [ # # # # : 0 : _Double<L> _new_val = convert<_Double<L>>(_arg);
# # # # #
# # # ]
4323 [ # # # # : 0 : if (_new_val.can_be_null()) {
# # # # #
# # # #
# ]
4324 : : M_insist_no_ternary_logic();
4325 [ # # # # : 0 : auto _new_val_pred = pred ? Select(*pred, _new_val, _Double<L>::Null()) : _new_val;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
4326 [ # # # # : 0 : auto [new_val, new_val_is_null_] = _new_val_pred.split();
# # # # #
# # # ]
4327 [ # # # # : 0 : const Var<Bool<L>> new_val_is_null(new_val_is_null_); // due to multiple uses
# # # # #
# # # ]
4328 : :
4329 [ # # # # : 0 : auto delta_absolute = new_val - avg;
# # # # #
# # # ]
4330 [ # # # # : 0 : auto delta_relative = delta_absolute / running_count;
# # # # #
# # # # #
# # # # #
# # # #
# ]
4331 : :
4332 [ # # # # : 0 : avg += Select(new_val_is_null,
# # # # #
# # # # #
# # # # #
# # # #
# ]
4333 [ # # # # : 0 : Double<L>(0.0), // ignore NULL
# # # # #
# # # ]
4334 : : delta_relative); // update old average with new value
4335 [ # # # # : 0 : is_null = is_null and new_val_is_null; // AVG is NULL iff all values are NULL
# # # # #
# # # # #
# # # # #
# # # #
# ]
4336 : 0 : } else {
4337 [ # # # # : 0 : auto _new_val_pred = pred ? Select(*pred, _new_val, avg) : _new_val;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
4338 [ # # # # : 0 : auto delta_absolute = _new_val_pred.insist_not_null() - avg;
# # # # #
# # # # #
# # # # #
# # # #
# ]
4339 [ # # # # : 0 : auto delta_relative = delta_absolute / running_count;
# # # # #
# # # # #
# # # # #
# # # #
# ]
4340 : :
4341 [ # # # # : 0 : avg += delta_relative; // update old average with new value
# # # # #
# # # ]
4342 [ # # # # : 0 : is_null.set_false(); // at least one non-NULL value is consumed
# # # # #
# # # ]
4343 : 0 : }
4344 : 0 : }
4345 : 0 : }
4346 : 0 : };
4347 [ # # # # : 0 : switch (CodeGenContext::Get().num_simd_lanes()) {
# # # ]
4348 : 0 : default: M_unreachable("unsupported number of SIMD lanes");
4349 : 0 : case 1: execute_pipeline.operator()<1>(); break;
4350 : 0 : case 2: execute_pipeline.operator()<2>(); break;
4351 : 0 : case 4: execute_pipeline.operator()<4>(); break;
4352 : 0 : case 8: execute_pipeline.operator()<8>(); break;
4353 : 0 : case 16: execute_pipeline.operator()<16>(); break;
4354 : 0 : case 32: execute_pipeline.operator()<32>(); break;
4355 : : }
4356 : 0 : },
4357 [ # # # # ]: 0 : /* teardown= */ teardown_t::Make_Without_Parent([&](){
4358 : 0 : auto execute_teardown = [&]<std::size_t L>(){
4359 : : #ifndef NDEBUG
4360 : 0 : M_insist(num_simd_lanes == L,
4361 : : "number of SIMD lanes in teardown callback must match the one in setup callback");
4362 : : #endif
4363 : :
4364 : : /*----- Get aggregates helper structures. -----*/
4365 : : using agg_t = agg_t_<false, L>;
4366 : : using agg_backup_t = agg_t_<true, L>;
4367 : 0 : auto agg_values = static_cast<agg_t*>(_agg_values);
4368 : 0 : auto agg_value_backups = static_cast<agg_backup_t*>(_agg_value_backups);
4369 : :
4370 : : /*----- Store local aggregate values to globals to access them in other function. -----*/
4371 [ # # # # : 0 : for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
# # # # #
# # # ]
4372 : 0 : auto &info = aggregates[idx];
4373 : :
4374 : 0 : bool is_min = false; ///< flag to indicate whether aggregate function is MIN
4375 [ # # # # : 0 : switch (info.fnid) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
4376 : : default:
4377 : 0 : M_unreachable("unsupported aggregate function");
4378 : : case m::Function::FN_MIN:
4379 : 0 : is_min = true; // set flag and delegate to MAX case
4380 : : case m::Function::FN_MAX: {
4381 : 0 : auto min_max = [&]<typename T>() {
4382 : 0 : auto &[min_max_backup, is_null_backup] = *M_notnull((
4383 : : std::get_if<
4384 : : std::pair<Global<PrimitiveExpr<T, L>>, Global<Bool<L>>>
4385 : : >(&agg_value_backups[idx])
4386 : : ));
4387 : 0 : std::tie(min_max_backup, is_null_backup) = *M_notnull((
4388 : : std::get_if<std::pair<Var<PrimitiveExpr<T, L>>, Var<Bool<L>>>>(&agg_values[idx])
4389 : : ));
4390 : 0 : };
4391 : 0 : auto &n = as<const Numeric>(*info.entry.type);
4392 [ # # # # : 0 : switch (n.kind) {
# # # # #
# # # # #
# # # # ]
4393 : : case Numeric::N_Int:
4394 : : case Numeric::N_Decimal:
4395 [ # # # # : 0 : switch (n.size()) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
4396 : 0 : default: M_unreachable("invalid size");
4397 : 0 : case 8: min_max.template operator()<int8_t >(); break;
4398 : 0 : case 16: min_max.template operator()<int16_t>(); break;
4399 : 0 : case 32: min_max.template operator()<int32_t>(); break;
4400 : 0 : case 64: min_max.template operator()<int64_t>(); break;
4401 : : }
4402 : 0 : break;
4403 : : case Numeric::N_Float:
4404 [ # # # # : 0 : if (n.size() <= 32)
# # # # #
# # # ]
4405 : 0 : min_max.template operator()<float>();
4406 : : else
4407 : 0 : min_max.template operator()<double>();
4408 : 0 : }
4409 : 0 : break;
4410 : : }
4411 : : case m::Function::FN_AVG: {
4412 : 0 : auto &[avg_backup, is_null_backup] = *M_notnull((
4413 : : std::get_if<std::pair<Global<Double<L>>, Global<Bool<L>>>>(&agg_value_backups[idx])
4414 : : ));
4415 : 0 : std::tie(avg_backup, is_null_backup) = *M_notnull((
4416 : : std::get_if<std::pair<Var<Double<L>>, Var<Bool<L>>>>(&agg_values[idx])
4417 : : ));
4418 : :
4419 : 0 : break;
4420 : : }
4421 : : case m::Function::FN_SUM: {
4422 : 0 : M_insist(info.args.size() == 1, "SUM aggregate function expects exactly one argument");
4423 : 0 : const auto &arg = *info.args[0];
4424 : :
4425 : 0 : auto sum = [&]<typename T>() {
4426 : 0 : auto &[sum_backup, is_null_backup] = *M_notnull((
4427 : : std::get_if<
4428 : : std::pair<Global<PrimitiveExpr<T, L>>, Global<Bool<L>>>
4429 : : >(&agg_value_backups[idx])
4430 : : ));
4431 : 0 : std::tie(sum_backup, is_null_backup) = *M_notnull((
4432 : : std::get_if<std::pair<Var<PrimitiveExpr<T, L>>, Var<Bool<L>>>>(&agg_values[idx])
4433 : : ));
4434 : 0 : };
4435 : 0 : auto &n = as<const Numeric>(*info.entry.type);
4436 [ # # # # : 0 : switch (n.kind) {
# # # # #
# # # # #
# # # # ]
4437 : : case Numeric::N_Int:
4438 : : case Numeric::N_Decimal:
4439 [ # # # # : 0 : switch (n.size()) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
4440 : 0 : default: M_unreachable("invalid size");
4441 : 0 : case 8: sum.template operator()<int8_t >(); break;
4442 : 0 : case 16: sum.template operator()<int16_t>(); break;
4443 : 0 : case 32: sum.template operator()<int32_t>(); break;
4444 : 0 : case 64: sum.template operator()<int64_t>(); break;
4445 : : }
4446 : 0 : break;
4447 : : case Numeric::N_Float:
4448 [ # # # # : 0 : if (n.size() <= 32)
# # # # #
# # # ]
4449 : 0 : sum.template operator()<float>();
4450 : : else
4451 : 0 : sum.template operator()<double>();
4452 : 0 : }
4453 : 0 : break;
4454 : : }
4455 : : case m::Function::FN_COUNT: {
4456 : 0 : auto &count_backup = *M_notnull(std::get_if<Global<I64<L>>>(&agg_value_backups[idx]));
4457 : 0 : count_backup = *M_notnull(std::get_if<Var<I64<L>>>(&agg_values[idx]));
4458 : :
4459 : 0 : break;
4460 : : }
4461 : : }
4462 : 0 : }
4463 : :
4464 : : /*----- Destroy created aggregates and their backups. -----*/
4465 [ # # # # : 0 : for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
# # # # #
# # # ]
4466 : 0 : agg_values[idx].~agg_t();
4467 : 0 : agg_value_backups[idx].~agg_backup_t();
4468 : 0 : }
4469 : :
4470 : : /*----- Free aggregates helper structures. -----*/
4471 [ # # # # : 0 : delete[] agg_values;
# # # # #
# # # ]
4472 [ # # # # : 0 : delete[] agg_value_backups;
# # # # #
# # # ]
4473 : 0 : };
4474 [ # # # # : 0 : switch (CodeGenContext::Get().num_simd_lanes()) {
# # # ]
4475 : 0 : default: M_unreachable("unsupported number of SIMD lanes");
4476 : 0 : case 1: execute_teardown.operator()<1>(); break;
4477 : 0 : case 2: execute_teardown.operator()<2>(); break;
4478 : 0 : case 4: execute_teardown.operator()<4>(); break;
4479 : 0 : case 8: execute_teardown.operator()<8>(); break;
4480 : 0 : case 16: execute_teardown.operator()<16>(); break;
4481 : 0 : case 32: execute_teardown.operator()<32>(); break;
4482 : : }
4483 : 0 : })
4484 : : );
4485 : 0 : }
4486 [ # # ]: 0 : aggregation_child_pipeline(); // call child function
4487 : :
4488 : : /*----- Emit setup code *before* possibly introducing temporary boolean variables to not overwrite them. -----*/
4489 [ # # ]: 0 : setup();
4490 : :
4491 : : /*----- Emit code to finalize aggregate computations. -----*/
4492 [ # # ]: 0 : for (auto &fn : finalize_aggregates)
4493 [ # # ]: 0 : fn();
4494 : :
4495 : : /*----- Add computed aggregates tuple to current environment. ----*/
4496 [ # # # # ]: 0 : auto &env = CodeGenContext::Get().env();
4497 [ # # # # : 0 : for (auto &e : M.aggregation.schema().deduplicate()) {
# # # # #
# ]
4498 [ # # # # ]: 0 : if (auto it = avg_aggregates.find(e.id);
4499 [ # # ]: 0 : it != avg_aggregates.end() and not it->second.compute_running_avg)
4500 : : { // AVG aggregates which is not yet computed, divide computed sum with computed count
4501 : 0 : auto &avg_info = it->second;
4502 [ # # ]: 0 : auto sum = results.get(avg_info.sum);
4503 [ # # # # : 0 : auto count = results.get<_I64x1>(avg_info.running_count).insist_not_null().to<double>();
# # ]
4504 [ # # # # ]: 0 : auto avg = convert<_Doublex1>(sum) / count;
4505 [ # # # # ]: 0 : M_insist(avg.can_be_null());
4506 [ # # ]: 0 : _Var<Doublex1> var(avg); // introduce variable s.t. uses only load from it
4507 [ # # # # : 0 : env.add(e.id, var);
# # ]
4508 : 0 : } else { // already computed aggregate
4509 [ # # ]: 0 : std::visit(overloaded {
4510 : 0 : [&]<typename T>(Expr<T> value) -> void {
4511 [ # # # # : 0 : if (value.can_be_null()) {
# # # # #
# # # #
# ]
4512 : 0 : Var<Expr<T>> var(value); // introduce variable s.t. uses only load from it
4513 [ # # # # : 0 : env.add(e.id, var);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
4514 : 0 : } else {
4515 : : /* introduce variable w/o NULL bit s.t. uses only load from it */
4516 [ # # # # : 0 : Var<PrimitiveExpr<T>> var(value.insist_not_null());
# # # # #
# # # #
# ]
4517 [ # # # # : 0 : env.add(e.id, Expr<T>(var));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
4518 : 0 : }
4519 : 0 : },
4520 : 0 : [](auto) -> void { M_unreachable("only scalar and non-string values must occur"); },
4521 : 0 : [](std::monostate) -> void { M_unreachable("invalid reference"); },
4522 [ # # ]: 0 : }, results.get(e.id)); // do not extract to be able to access for not-yet-computed AVG aggregates
4523 : : }
4524 : : }
4525 : :
4526 : : /*----- Resume pipeline. -----*/
4527 [ # # # # ]: 0 : CodeGenContext::Get().set_num_simd_lanes(1); // since only a single tuple is produced
4528 [ # # ]: 0 : pipeline();
4529 : :
4530 : : /*----- Emit teardown code. -----*/
4531 [ # # ]: 0 : teardown();
4532 : 0 : }
4533 : :
4534 : :
4535 : : /*======================================================================================================================
4536 : : * Sorting
4537 : : *====================================================================================================================*/
4538 : :
4539 : : template<bool CmpPredicated>
4540 : 0 : ConditionSet Quicksort<CmpPredicated>::pre_condition(std::size_t child_idx, const std::tuple<const SortingOperator*>&)
4541 : : {
4542 : 0 : M_insist(child_idx == 0);
4543 : :
4544 : 0 : ConditionSet pre_cond;
4545 : :
4546 : : /*----- Sorting does not support SIMD. -----*/
4547 [ # # # # : 0 : pre_cond.add_condition(NoSIMD());
# # # # ]
4548 : :
4549 : 0 : return pre_cond;
4550 [ # # # # ]: 0 : }
4551 : :
4552 : : template<bool CmpPredicated>
4553 : 0 : ConditionSet Quicksort<CmpPredicated>::post_condition(const Match<Quicksort> &M)
4554 : : {
4555 : 0 : ConditionSet post_cond;
4556 : :
4557 : : /*----- Quicksort does not introduce predication. -----*/
4558 [ # # # # : 0 : post_cond.add_condition(Predicated(false));
# # # # ]
4559 : :
4560 : : /*----- Quicksort does sort the data. -----*/
4561 : 0 : Sortedness::order_t orders;
4562 [ # # # # : 0 : for (auto &o : M.sorting.order_by()) {
# # # # ]
4563 [ # # # # ]: 0 : Schema::Identifier id(o.first);
4564 [ # # # # : 0 : if (orders.find(id) == orders.cend())
# # # # #
# # # ]
4565 [ # # # # : 0 : orders.add(std::move(id), o.second ? Sortedness::O_ASC : Sortedness::O_DESC);
# # # # ]
4566 : 0 : }
4567 [ # # # # : 0 : post_cond.add_condition(Sortedness(std::move(orders)));
# # # # ]
4568 : :
4569 : : /*----- Sorting does not introduce SIMD. -----*/
4570 [ # # # # : 0 : post_cond.add_condition(NoSIMD());
# # # # ]
4571 : :
4572 : 0 : return post_cond;
4573 [ # # # # ]: 0 : }
4574 : :
4575 : : template<bool CmpPredicated>
4576 : 0 : void Quicksort<CmpPredicated>::execute(const Match<Quicksort> &M, setup_t setup, pipeline_t pipeline,
4577 : : teardown_t teardown)
4578 : : {
4579 : : /*----- Create infinite buffer to materialize the current results but resume the pipeline later. -----*/
4580 : 0 : M_insist(bool(M.materializing_factory), "`wasm::Quicksort` must have a factory for the materialized child");
4581 [ # # # # ]: 0 : const auto buffer_schema = M.child->get_matched_root().schema().drop_constants().deduplicate();
4582 [ # # # # : 0 : const auto sorting_schema = M.sorting.schema().drop_constants().deduplicate();
# # # # #
# # # ]
4583 [ # # # # ]: 0 : GlobalBuffer buffer(
4584 : 0 : buffer_schema, *M.materializing_factory, false, 0, std::move(setup), std::move(pipeline), std::move(teardown)
4585 : : );
4586 : :
4587 : : /*----- Create child function. -----*/
4588 [ # # # # : 0 : FUNCTION(sorting_child_pipeline, void(void)) // create function for pipeline
# # # # #
# # # # #
# # ]
4589 : : {
4590 [ # # # # : 0 : auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
# # # # ]
4591 : :
4592 [ # # # # ]: 0 : M.child->execute(
4593 [ # # # # ]: 0 : /* setup= */ setup_t::Make_Without_Parent([&](){ buffer.setup(); }),
4594 : 0 : /* pipeline= */ [&](){ buffer.consume(); },
4595 [ # # # # ]: 0 : /* teardown= */ teardown_t::Make_Without_Parent([&](){ buffer.teardown(); })
4596 : : );
4597 : 0 : }
4598 [ # # # # ]: 0 : sorting_child_pipeline(); // call child function
4599 : :
4600 : : /*----- Invoke quicksort algorithm with buffer to sort. -----*/
4601 [ # # # # : 0 : quicksort<CmpPredicated>(buffer, M.sorting.order_by());
# # # # ]
4602 : :
4603 : : /*----- Process sorted buffer. -----*/
4604 [ # # # # ]: 0 : buffer.resume_pipeline(sorting_schema);
4605 : 0 : }
4606 : :
4607 : 0 : ConditionSet NoOpSorting::pre_condition(std::size_t child_idx,
4608 : : const std::tuple<const SortingOperator*> &partial_inner_nodes)
4609 : : {
4610 : 0 : M_insist(child_idx == 0);
4611 : :
4612 : 0 : ConditionSet pre_cond;
4613 : :
4614 : : /*----- NoOpSorting, i.e. a noop to match sorting, needs the data already sorted. -----*/
4615 : 0 : Sortedness::order_t orders;
4616 [ # # # # ]: 0 : for (auto &o : std::get<0>(partial_inner_nodes)->order_by()) {
4617 [ # # ]: 0 : Schema::Identifier id(o.first);
4618 [ # # # # : 0 : if (orders.find(id) == orders.cend())
# # ]
4619 [ # # # # ]: 0 : orders.add(std::move(id), o.second ? Sortedness::O_ASC : Sortedness::O_DESC);
4620 : 0 : }
4621 [ # # # # ]: 0 : pre_cond.add_condition(Sortedness(std::move(orders)));
4622 : :
4623 : 0 : return pre_cond;
4624 [ # # ]: 0 : }
4625 : :
4626 : 0 : void NoOpSorting::execute(const Match<NoOpSorting> &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
4627 : : {
4628 [ # # ]: 0 : M.child->execute(std::move(setup), std::move(pipeline), std::move(teardown));
4629 : 0 : }
4630 : :
4631 : :
4632 : : /*======================================================================================================================
4633 : : * Join
4634 : : *====================================================================================================================*/
4635 : :
4636 : : template<bool Predicated>
4637 : 0 : ConditionSet NestedLoopsJoin<Predicated>::pre_condition(std::size_t, const std::tuple<const JoinOperator*>&)
4638 : : {
4639 : 0 : ConditionSet pre_cond;
4640 : :
4641 : : /*----- Nested-loops join does not support SIMD. -----*/
4642 [ # # # # : 0 : pre_cond.add_condition(NoSIMD());
# # # # ]
4643 : :
4644 : 0 : return pre_cond;
4645 [ # # # # ]: 0 : }
4646 : :
4647 : : template<bool Predicated>
4648 : 0 : ConditionSet NestedLoopsJoin<Predicated>::adapt_post_conditions(
4649 : : const Match<NestedLoopsJoin>&,
4650 : : std::vector<std::reference_wrapper<const ConditionSet>> &&post_cond_children)
4651 : : {
4652 : 0 : M_insist(post_cond_children.size() >= 2);
4653 : :
4654 : 0 : ConditionSet post_cond(post_cond_children.back().get()); // preserve conditions of right-most child
4655 : :
4656 : : if constexpr (Predicated) {
4657 : : /*----- Predicated nested-loops join introduces predication. -----*/
4658 [ # # # # ]: 0 : post_cond.add_or_replace_condition(m::Predicated(true));
4659 : : }
4660 : :
4661 : 0 : return post_cond;
4662 [ # # # # ]: 0 : }
4663 : :
4664 : : template<bool Predicated>
4665 : 0 : double NestedLoopsJoin<Predicated>::cost(const Match<NestedLoopsJoin> &M)
4666 : : {
4667 : 0 : double cost = 1;
4668 [ # # # # ]: 0 : for (auto &child : M.children)
4669 : 0 : cost *= child->get_matched_root().info().estimated_cardinality;
4670 : 0 : return cost;
4671 : : }
4672 : :
4673 : : template<bool Predicated>
4674 : 0 : void NestedLoopsJoin<Predicated>::execute(const Match<NestedLoopsJoin> &M, setup_t setup, pipeline_t pipeline,
4675 : : teardown_t teardown)
4676 : : {
4677 : 0 : const auto num_left_children = M.children.size() - 1; // all children but right-most one
4678 : :
4679 : 0 : std::vector<Schema> schemas; // to own adapted schemas
4680 [ # # # # ]: 0 : schemas.reserve(num_left_children);
4681 : 0 : std::vector<GlobalBuffer> buffers;
4682 [ # # # # ]: 0 : buffers.reserve(num_left_children);
4683 : :
4684 : : /*----- Process all but right-most child. -----*/
4685 [ # # # # ]: 0 : for (std::size_t i = 0; i < num_left_children; ++i) {
4686 : : /*----- Create function for each child. -----*/
4687 [ # # # # : 0 : FUNCTION(nested_loop_join_child_pipeline, void(void))
# # # # #
# # # # #
# # ]
4688 : : {
4689 [ # # # # : 0 : auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
# # # # ]
4690 : :
4691 : : /*----- Create infinite buffer to materialize the current results. -----*/
4692 [ # # # # ]: 0 : M_insist(bool(M.materializing_factories_[i]),
4693 : : "`wasm::NestedLoopsJoin` must have a factory for each materialized child");
4694 [ # # # # ]: 0 : const auto &schema = schemas.emplace_back(
4695 [ # # # # : 0 : M.children[i]->get_matched_root().schema().drop_constants().deduplicate()
# # # # #
# # # # #
# # ]
4696 : : );
4697 [ # # # # ]: 0 : if (i == 0) {
4698 : : /*----- Exactly one child (here left-most one) checks join predicate and resumes pipeline. -----*/
4699 [ # # # # ]: 0 : buffers.emplace_back(
4700 : 0 : /* schema= */ schema,
4701 : 0 : /* factory= */ *M.materializing_factories_[i],
4702 : 0 : /* load_simdfied= */ false,
4703 : 0 : /* num_tuples= */ 0, // i.e. infinite
4704 [ # # # # ]: 0 : /* setup= */ setup_t::Make_Without_Parent(),
4705 : 0 : /* pipeline= */ [&, pipeline=std::move(pipeline)](){
4706 : : if constexpr (Predicated) {
4707 : 0 : CodeGenContext::Get().env().add_predicate(M.join.predicate());
4708 : 0 : pipeline();
4709 : : } else {
4710 : 0 : M_insist(CodeGenContext::Get().num_simd_lanes() == 1, "invalid number of SIMD lanes");
4711 [ # # # # ]: 0 : IF (CodeGenContext::Get().env().compile<_Boolx1>(M.join.predicate()).is_true_and_not_null()) {
4712 : 0 : pipeline();
4713 : 0 : };
4714 : : }
4715 : 0 : },
4716 [ # # # # ]: 0 : /* teardown= */ teardown_t::Make_Without_Parent()
4717 : : );
4718 : 0 : } else {
4719 : : /*----- All but exactly one child (here left-most one) load lastly inserted buffer again. -----*/
4720 : : /* All buffers are "connected" with each other by setting the pipeline callback as calling the
4721 : : * `resume_pipeline_inline()` method of the lastly inserted buffer. Therefore, calling
4722 : : * `resume_pipeline_inline()` on the lastly inserted buffer will load one tuple from it, recursively
4723 : : * call `resume_pipeline_inline()` on the buffer created before that which again loads one tuple from
4724 : : * it, and so on until the buffer inserted first (here the one of the left-most child) will load one
4725 : : * of its tuples and check the join predicate for this one cartesian-product-combination of result
4726 : : * tuples. */
4727 [ # # # # ]: 0 : buffers.emplace_back(
4728 : 0 : /* schema= */ schema,
4729 : 0 : /* factory= */ *M.materializing_factories_[i],
4730 : 0 : /* load_simdfied= */ false,
4731 : 0 : /* num_tuples= */ 0, // i.e. infinite
4732 [ # # # # ]: 0 : /* setup= */ setup_t::Make_Without_Parent(),
4733 : 0 : /* pipeline= */ [&](){ buffers.back().resume_pipeline_inline(); },
4734 [ # # # # ]: 0 : /* teardown= */ teardown_t::Make_Without_Parent()
4735 : : );
4736 : : }
4737 : :
4738 : : /*----- Materialize the current result tuple in pipeline. -----*/
4739 [ # # # # ]: 0 : M.children[i]->execute(
4740 [ # # # # ]: 0 : /* setup= */ setup_t::Make_Without_Parent([&](){ buffers.back().setup(); }),
4741 : 0 : /* pipeline= */ [&](){ buffers.back().consume(); },
4742 [ # # # # ]: 0 : /* teardown= */ teardown_t::Make_Without_Parent([&](){ buffers.back().teardown(); })
4743 : : );
4744 : 0 : }
4745 [ # # # # ]: 0 : nested_loop_join_child_pipeline(); // call child function
4746 : 0 : }
4747 : :
4748 : : /*----- Process right-most child. -----*/
4749 [ # # # # : 0 : M.children.back()->execute(
# # # # ]
4750 : 0 : /* setup= */ std::move(setup),
4751 : 0 : /* pipeline= */ [&](){ buffers.back().resume_pipeline_inline(); },
4752 : 0 : /* teardown= */ std::move(teardown)
4753 : : );
4754 : 0 : }
4755 : :
4756 : : template<bool UniqueBuild, bool Predicated>
4757 : 0 : ConditionSet SimpleHashJoin<UniqueBuild, Predicated>::pre_condition(
4758 : : std::size_t,
4759 : : const std::tuple<const JoinOperator*, const Wildcard*, const Wildcard*> &partial_inner_nodes)
4760 : : {
4761 : 0 : ConditionSet pre_cond;
4762 : :
4763 : : /*----- Simple hash join can only be used for binary joins on equi-predicates. -----*/
4764 : 0 : auto &join = *std::get<0>(partial_inner_nodes);
4765 [ # # # # : 0 : if (not join.predicate().is_equi())
# # # # #
# # # # #
# # # # #
# # # #
# ]
4766 [ # # # # : 0 : return ConditionSet::Make_Unsatisfiable();
# # # # ]
4767 : :
4768 : : if constexpr (UniqueBuild) {
4769 : : /*----- Decompose each clause of the join predicate of the form `A.x = B.y` into parts `A.x` and `B.y`. -----*/
4770 : 0 : auto &build = *std::get<1>(partial_inner_nodes);
4771 [ # # # # : 0 : for (auto &clause : join.predicate()) {
# # # # ]
4772 [ # # # # ]: 0 : M_insist(clause.size() == 1, "invalid equi-predicate");
4773 : 0 : auto &literal = clause[0];
4774 [ # # # # : 0 : auto &binary = as<const BinaryExpr>(literal.expr());
# # # # ]
4775 [ # # # # : 0 : M_insist((not literal.negative() and binary.tok == TK_EQUAL) or
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
4776 : : (literal.negative() and binary.tok == TK_BANG_EQUAL), "invalid equi-predicate");
4777 [ # # # # : 0 : M_insist(is<const Designator>(binary.lhs), "invalid equi-predicate");
# # # # ]
4778 [ # # # # : 0 : M_insist(is<const Designator>(binary.rhs), "invalid equi-predicate");
# # # # ]
4779 [ # # # # : 0 : Schema::Identifier id_first(*binary.lhs), id_second(*binary.rhs);
# # # # ]
4780 [ # # # # : 0 : const auto &entry_build = build.schema().has(id_first) ? build.schema()[id_first].second
# # # # #
# # # # #
# # # # #
# ]
4781 [ # # # # : 0 : : build.schema()[id_second].second;
# # # # ]
4782 : :
4783 : : /*----- Unique simple hash join can only be used on unique build key. -----*/
4784 [ # # # # : 0 : if (not entry_build.unique())
# # # # ]
4785 [ # # # # ]: 0 : return ConditionSet::Make_Unsatisfiable();
4786 [ # # # # ]: 0 : }
4787 : : }
4788 : :
4789 : : /*----- Simple hash join does not support SIMD. -----*/
4790 [ # # # # : 0 : pre_cond.add_condition(NoSIMD());
# # # # #
# # # # #
# # ]
4791 : :
4792 : 0 : return pre_cond;
4793 : 0 : }
4794 : :
4795 : : template<bool UniqueBuild, bool Predicated>
4796 : 0 : ConditionSet SimpleHashJoin<UniqueBuild, Predicated>::adapt_post_conditions(
4797 : : const Match<SimpleHashJoin>&,
4798 : : std::vector<std::reference_wrapper<const ConditionSet>> &&post_cond_children)
4799 : : {
4800 : 0 : M_insist(post_cond_children.size() == 2);
4801 : :
4802 : 0 : ConditionSet post_cond(post_cond_children[1].get()); // preserve conditions of right child
4803 : :
4804 : : if constexpr (Predicated) {
4805 : : /*----- Predicated simple hash join introduces predication. -----*/
4806 [ # # # # : 0 : post_cond.add_or_replace_condition(m::Predicated(true));
# # # # ]
4807 : : } else {
4808 : : /*----- Branching simple hash join does not introduce predication (it is already handled by the hash table). -*/
4809 [ # # # # : 0 : post_cond.add_or_replace_condition(m::Predicated(false));
# # # # ]
4810 : : }
4811 : :
4812 : 0 : return post_cond;
4813 [ # # # # : 0 : }
# # # # ]
4814 : :
4815 : : template<bool UniqueBuild, bool Predicated>
4816 : 0 : double SimpleHashJoin<UniqueBuild, Predicated>::cost(const Match<SimpleHashJoin> &M)
4817 : : {
4818 [ # # # # : 0 : if (options::simple_hash_join_ordering_strategy == option_configs::OrderingStrategy::BUILD_ON_LEFT)
# # # # ]
4819 : 0 : return (M.build.id() == M.children[0]->get_matched_root().id() ? 1.0 : 2.0) + (UniqueBuild ? 0.0 : 0.1);
4820 [ # # # # : 0 : else if (options::simple_hash_join_ordering_strategy == option_configs::OrderingStrategy::BUILD_ON_RIGHT)
# # # # ]
4821 : 0 : return M.build.id() == M.children[1]->get_matched_root().id() ? 1.0 : 2.0 + (UniqueBuild ? 0.0 : 0.1);
4822 : : else
4823 : 0 : return 1.5 * M.build.info().estimated_cardinality +
4824 : 0 : (UniqueBuild ? 1.0 : 1.1) * M.probe.info().estimated_cardinality;
4825 : 0 : }
4826 : :
4827 : : template<bool UniqueBuild, bool Predicated>
4828 : 0 : void SimpleHashJoin<UniqueBuild, Predicated>::execute(const Match<SimpleHashJoin> &M, setup_t setup,
4829 : : pipeline_t pipeline, teardown_t teardown)
4830 : : {
4831 : : // TODO: determine setup
4832 : 0 : const uint64_t PAYLOAD_SIZE_THRESHOLD_IN_BITS =
4833 : 0 : M.use_in_place_values ? std::numeric_limits<uint64_t>::max() : 0;
4834 : :
4835 [ # # # # : 0 : M_insist(((M.join.schema() | M.join.predicate().get_required()) & M.build.schema()) == M.build.schema());
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
4836 [ # # # # : 0 : M_insist(M.build.schema().drop_constants() == M.build.schema());
# # # # #
# # # # #
# # # # #
# # # #
# ]
4837 : 0 : const auto ht_schema = M.build.schema().deduplicate();
4838 : :
4839 : : /*----- Decompose each clause of the join predicate of the form `A.x = B.y` into parts `A.x` and `B.y`. -----*/
4840 [ # # # # : 0 : const auto [build_keys, probe_keys] = decompose_equi_predicate(M.join.predicate(), ht_schema);
# # # # #
# # # # #
# # ]
4841 : :
4842 : : /*----- Compute payload IDs and its total size in bits (ignoring padding). -----*/
4843 : 0 : std::vector<Schema::Identifier> payload_ids;
4844 : 0 : uint64_t payload_size_in_bits = 0;
4845 [ # # # # : 0 : for (auto &e : ht_schema) {
# # # # #
# # # # #
# # # # #
# # # #
# ]
4846 [ # # # # : 0 : if (not contains(build_keys, e.id)) {
# # # # #
# # # # #
# # ]
4847 [ # # # # : 0 : payload_ids.push_back(e.id);
# # # # ]
4848 [ # # # # : 0 : payload_size_in_bits += e.type->size();
# # # # ]
4849 : 0 : }
4850 : : }
4851 : :
4852 : : /*----- Compute initial capacity of hash table. -----*/
4853 [ # # # # : 0 : uint32_t initial_capacity = compute_initial_ht_capacity(M.build, M.load_factor);
# # # # ]
4854 : :
4855 : : /*----- Create hash table for build child. -----*/
4856 : 0 : std::unique_ptr<HashTable> ht;
4857 : 0 : std::vector<HashTable::index_t> build_key_indices;
4858 [ # # # # : 0 : for (auto &build_key : build_keys)
# # # # ]
4859 [ # # # # : 0 : build_key_indices.push_back(ht_schema[build_key].first);
# # # # #
# # # # #
# # ]
4860 [ # # # # : 0 : if (M.use_open_addressing_hashing) {
# # # # ]
4861 [ # # # # : 0 : if (payload_size_in_bits < PAYLOAD_SIZE_THRESHOLD_IN_BITS)
# # # # ]
4862 [ # # # # : 0 : ht = std::make_unique<GlobalOpenAddressingInPlaceHashTable>(ht_schema, std::move(build_key_indices),
# # # # ]
4863 : : initial_capacity);
4864 : : else
4865 [ # # # # : 0 : ht = std::make_unique<GlobalOpenAddressingOutOfPlaceHashTable>(ht_schema, std::move(build_key_indices),
# # # # ]
4866 : : initial_capacity);
4867 [ # # # # : 0 : if (M.use_quadratic_probing)
# # # # ]
4868 [ # # # # : 0 : as<OpenAddressingHashTableBase>(*ht).set_probing_strategy<QuadraticProbing>();
# # # # #
# # # # #
# # ]
4869 : : else
4870 [ # # # # : 0 : as<OpenAddressingHashTableBase>(*ht).set_probing_strategy<LinearProbing>();
# # # # #
# # # # #
# # ]
4871 : 0 : } else {
4872 [ # # # # : 0 : ht = std::make_unique<GlobalChainedHashTable>(ht_schema, std::move(build_key_indices), initial_capacity);
# # # # ]
4873 : : }
4874 : :
4875 : : /*----- Create function for build child. -----*/
4876 [ # # # # : 0 : FUNCTION(simple_hash_join_child_pipeline, void(void)) // create function for pipeline
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
4877 : : {
4878 [ # # # # : 0 : auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
# # # # #
# # # # #
# # ]
4879 : :
4880 [ # # # # : 0 : M.children[0]->execute(
# # # # ]
4881 [ # # # # : 0 : /* setup= */ setup_t::Make_Without_Parent([&](){
# # # # ]
4882 : 0 : ht->setup();
4883 : 0 : ht->set_high_watermark(M.load_factor);
4884 : 0 : }),
4885 [ # # # # : 0 : /* pipeline= */ [&](){
# # # # #
# # # # #
# # ]
4886 : 0 : auto &env = CodeGenContext::Get().env();
4887 : :
4888 : 0 : std::optional<Boolx1> build_key_not_null;
4889 [ # # # # : 0 : for (auto &build_key : build_keys) {
# # # # ]
4890 [ # # # # : 0 : auto val = env.get(build_key);
# # # # ]
4891 [ # # # # : 0 : if (build_key_not_null)
# # # # ]
4892 [ # # # # : 0 : build_key_not_null.emplace(*build_key_not_null and not_null(val));
# # # # #
# # # # #
# # # # #
# # # #
# ]
4893 : : else
4894 [ # # # # : 0 : build_key_not_null.emplace(not_null(val));
# # # # #
# # # # #
# # ]
4895 : 0 : }
4896 [ # # # # : 0 : M_insist(bool(build_key_not_null));
# # # # ]
4897 [ # # # # : 0 : IF (*build_key_not_null) { // TODO: predicated version
# # # # #
# # # # #
# # ]
4898 : : /*----- Insert key. -----*/
4899 : 0 : std::vector<SQL_t> key;
4900 [ # # # # : 0 : for (auto &build_key : build_keys)
# # # # ]
4901 [ # # # # : 0 : key.emplace_back(env.get(build_key));
# # # # #
# # # # #
# # ]
4902 [ # # # # : 0 : auto entry = ht->emplace(std::move(key));
# # # # ]
4903 : :
4904 : : /*----- Insert payload. -----*/
4905 [ # # # # : 0 : for (auto &id : payload_ids) {
# # # # ]
4906 [ # # # # : 0 : std::visit(overloaded {
# # # # ]
4907 [ # # # # : 0 : [&]<sql_type T>(HashTable::reference_t<T> &&r) -> void { r = env.extract<T>(id); },
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
4908 : 0 : [](std::monostate) -> void { M_unreachable("invalid reference"); },
4909 [ # # # # : 0 : }, entry.extract(id));
# # # # ]
4910 : : }
4911 : 0 : };
4912 : 0 : },
4913 [ # # # # : 0 : /* teardown= */ teardown_t::Make_Without_Parent([&](){ ht->teardown(); })
# # # # ]
4914 : : );
4915 : 0 : }
4916 [ # # # # : 0 : simple_hash_join_child_pipeline(); // call child function
# # # # ]
4917 : :
4918 [ # # # # : 0 : M.children[1]->execute(
# # # # ]
4919 [ # # # # : 0 : /* setup= */ setup_t(std::move(setup), [&](){ ht->setup(); }),
# # # # ]
4920 [ # # # # : 0 : /* pipeline= */ [&, pipeline=std::move(pipeline)](){
# # # # #
# # # # #
# # # # #
# # # #
# ]
4921 : 0 : auto &env = CodeGenContext::Get().env();
4922 : :
4923 : 0 : auto emit_tuple_and_resume_pipeline = [&, pipeline=std::move(pipeline)](HashTable::const_entry_t entry){
4924 : : /*----- Add found entry from hash table, i.e. from build child, to current environment. -----*/
4925 [ # # # # : 0 : for (auto &e : ht_schema) {
# # # # ]
4926 [ # # # # : 0 : if (not entry.has(e.id)) { // entry may not contain build key in case `ht->find()` was used
# # # # ]
4927 : 0 : M_insist(contains(build_keys, e.id));
4928 : 0 : M_insist(env.has(e.id), "build key must already be contained in the current environment");
4929 : 0 : continue;
4930 : : }
4931 : :
4932 [ # # # # : 0 : std::visit(overloaded {
# # # # #
# # # # #
# # ]
4933 : 0 : [&]<typename T>(HashTable::const_reference_t<Expr<T>> &&r) -> void {
4934 : 0 : Expr<T> value = r;
4935 [ # # # # : 0 : if (value.can_be_null()) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
4936 [ # # # # : 0 : Var<Expr<T>> var(value); // introduce variable s.t. uses only load from it
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
4937 [ # # # # : 0 : env.add(e.id, var);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
4938 : 0 : } else {
4939 : : /* introduce variable w/o NULL bit s.t. uses only load from it */
4940 [ # # # # : 0 : Var<PrimitiveExpr<T>> var(value.insist_not_null());
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
4941 [ # # # # : 0 : env.add(e.id, Expr<T>(var));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
4942 : 0 : }
4943 : 0 : },
4944 : 0 : [&](HashTable::const_reference_t<NChar> &&r) -> void {
4945 : 0 : NChar value(r);
4946 [ # # # # : 0 : Var<Ptr<Charx1>> var(value.val()); // introduce variable s.t. uses only load from it
# # # # #
# # # # #
# # ]
4947 [ # # # # : 0 : env.add(e.id, NChar(var, value.can_be_null(), value.length(),
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
4948 : 0 : value.guarantees_terminating_nul()));
4949 : 0 : },
4950 : 0 : [](std::monostate) -> void { M_unreachable("invalid reference"); },
4951 : 0 : }, entry.extract(e.id));
4952 : : }
4953 : :
4954 : : /*----- Resume pipeline. -----*/
4955 : 0 : pipeline();
4956 : 0 : };
4957 : :
4958 : : /* TODO: may check for NULL on probe keys as well, branching + predicated version */
4959 : : /*----- Probe with probe key. -----*/
4960 : 0 : std::vector<SQL_t> key;
4961 [ # # # # : 0 : for (auto &probe_key : probe_keys)
# # # # ]
4962 [ # # # # : 0 : key.emplace_back(env.get(probe_key));
# # # # #
# # # # #
# # ]
4963 : : if constexpr (UniqueBuild) {
4964 : : /*----- Add build key to current environment since `ht->find()` will only return the payload values. -----*/
4965 [ # # # # ]: 0 : for (auto build_it = build_keys.cbegin(), probe_it = probe_keys.cbegin(); build_it != build_keys.cend();
4966 : 0 : ++build_it, ++probe_it)
4967 : : {
4968 [ # # # # ]: 0 : M_insist(probe_it != probe_keys.cend());
4969 [ # # # # : 0 : if (not env.has(*build_it)) // skip duplicated build keys and only add first occurrence
# # # # ]
4970 [ # # # # : 0 : env.add(*build_it, env.get(*probe_it)); // since build and probe keys match for join partners
# # # # #
# # # ]
4971 : 0 : }
4972 : :
4973 : : /*----- Try to find the *single* possible join partner. -----*/
4974 [ # # # # ]: 0 : auto p = ht->find(std::move(key));
4975 : 0 : auto &entry = p.first;
4976 : 0 : auto &found = p.second;
4977 : : if constexpr (Predicated) {
4978 [ # # # # ]: 0 : env.add_predicate(found);
4979 [ # # # # ]: 0 : emit_tuple_and_resume_pipeline(std::move(entry));
4980 : : } else {
4981 [ # # ]: 0 : IF (found) {
4982 [ # # ]: 0 : emit_tuple_and_resume_pipeline(std::move(entry));
4983 : 0 : };
4984 : : }
4985 : 0 : } else {
4986 : : /*----- Search for *all* join partners. -----*/
4987 [ # # # # : 0 : ht->for_each_in_equal_range(std::move(key), std::move(emit_tuple_and_resume_pipeline), Predicated);
# # # # ]
4988 : : }
4989 : 0 : },
4990 [ # # # # : 0 : /* teardown= */ teardown_t(std::move(teardown), [&](){ ht->teardown(); })
# # # # ]
4991 : : );
4992 : 0 : }
4993 : :
4994 : : template<bool SortLeft, bool SortRight, bool Predicated, bool CmpPredicated>
4995 : 0 : ConditionSet SortMergeJoin<SortLeft, SortRight, Predicated, CmpPredicated>::pre_condition(
4996 : : std::size_t child_idx,
4997 : : const std::tuple<const JoinOperator*, const Wildcard*, const Wildcard*> &partial_inner_nodes)
4998 : : {
4999 : 0 : ConditionSet pre_cond;
5000 : :
5001 : : /*----- Sort merge join can only be used for binary joins on conjunctions of equi-predicates. -----*/
5002 : 0 : auto &join = *std::get<0>(partial_inner_nodes);
5003 [ # # # # : 0 : if (not join.predicate().is_equi())
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
5004 [ # # # # : 0 : return ConditionSet::Make_Unsatisfiable();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5005 : :
5006 : : /*----- Decompose each clause of the join predicate of the form `A.x = B.y` into parts `A.x` and `B.y`. -----*/
5007 : 0 : auto parent = std::get<1>(partial_inner_nodes);
5008 : 0 : auto child = std::get<2>(partial_inner_nodes);
5009 [ # # # # : 0 : M_insist(parent);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5010 [ # # # # : 0 : M_insist(child_idx != 1 or child);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
5011 : 0 : std::vector<Schema::Identifier> keys_parent, keys_child;
5012 [ # # # # : 0 : for (auto &clause : join.predicate()) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
5013 [ # # # # : 0 : M_insist(clause.size() == 1, "invalid equi-predicate");
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5014 : 0 : auto &literal = clause[0];
5015 [ # # # # : 0 : auto &binary = as<const BinaryExpr>(literal.expr());
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
5016 [ # # # # : 0 : M_insist((not literal.negative() and binary.tok == TK_EQUAL) or
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
5017 : : (literal.negative() and binary.tok == TK_BANG_EQUAL), "invalid equi-predicate");
5018 [ # # # # : 0 : M_insist(is<const Designator>(binary.lhs), "invalid equi-predicate");
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
5019 [ # # # # : 0 : M_insist(is<const Designator>(binary.rhs), "invalid equi-predicate");
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
5020 [ # # # # : 0 : Schema::Identifier id_first(*binary.lhs), id_second(*binary.rhs);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
5021 [ # # # # : 0 : auto dummy = Schema::entry_type::CreateArtificial(); ///< dummy entry used in case of `child_idx` != 1, i.e. `child` is not yet set
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5022 [ # # # # : 0 : const auto &[entry_parent, entry_child] = parent->schema().has(id_first)
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
5023 [ # # # # : 0 : ? std::make_pair(parent->schema()[id_first].second, child_idx == 1 ? child->schema()[id_second].second : std::move(dummy))
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
5024 [ # # # # : 0 : : std::make_pair(parent->schema()[id_second].second, child_idx == 1 ? child->schema()[id_first].second : std::move(dummy));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
5025 [ # # # # : 0 : keys_parent.push_back(entry_parent.id);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5026 [ # # # # : 0 : keys_child.push_back(entry_child.id);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5027 : :
5028 : : /*----- Sort merge join can only be used on unique parent key. -----*/
5029 [ # # # # : 0 : if (not entry_parent.unique())
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
5030 [ # # # # : 0 : return ConditionSet::Make_Unsatisfiable();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5031 [ # # # # : 0 : }
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5032 [ # # # # : 0 : M_insist(keys_parent.size() == keys_child.size(), "number of found IDs differ");
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5033 [ # # # # : 0 : M_insist(not keys_parent.empty(), "must find at least one ID");
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5034 : :
5035 : : if constexpr (not SortLeft or not SortRight) {
5036 : : /*----- Sort merge join without sorting needs its data sorted on the respective key. -----*/
5037 : 0 : Sortedness::order_t orders;
5038 [ # # # # : 0 : M_insist(child_idx < 2);
# # # # #
# # # # #
# # # # #
# # # #
# ]
5039 [ # # # # : 0 : if (not SortLeft and child_idx == 0) {
# # # # #
# # # # #
# # ]
5040 [ # # # # : 0 : for (auto &key_parent : keys_parent) {
# # # # #
# # # # #
# # ]
5041 [ # # # # : 0 : if (orders.find(key_parent) == orders.cend())
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
5042 [ # # # # : 0 : orders.add(key_parent, Sortedness::O_ASC); // TODO: support different order
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5043 : : }
5044 [ # # # # : 0 : } else if (not SortRight and child_idx == 1) {
# # # # #
# # # # #
# # ]
5045 [ # # # # : 0 : for (auto &key_child : keys_child) {
# # # # #
# # # # #
# # ]
5046 [ # # # # : 0 : if (orders.find(key_child) == orders.cend())
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
5047 [ # # # # : 0 : orders.add(key_child, Sortedness::O_ASC); // TODO: support different order
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5048 : : }
5049 : 0 : }
5050 [ # # # # : 0 : pre_cond.add_condition(Sortedness(std::move(orders)));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
5051 : 0 : }
5052 : :
5053 : : /*----- Sort merge join does not support SIMD. -----*/
5054 [ # # # # : 0 : pre_cond.add_condition(NoSIMD());
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
5055 : :
5056 : 0 : return pre_cond;
5057 : 0 : }
5058 : :
5059 : : template<bool SortLeft, bool SortRight, bool Predicated, bool CmpPredicated>
5060 : 0 : ConditionSet SortMergeJoin<SortLeft, SortRight, Predicated, CmpPredicated>::adapt_post_conditions(
5061 : : const Match<SortMergeJoin> &M,
5062 : : std::vector<std::reference_wrapper<const ConditionSet>> &&post_cond_children)
5063 : : {
5064 : 0 : M_insist(post_cond_children.size() == 2);
5065 : :
5066 : 0 : ConditionSet post_cond;
5067 : :
5068 : : if constexpr (Predicated) {
5069 : : /*----- Predicated sort merge join introduces predication. -----*/
5070 [ # # # # : 0 : post_cond.add_or_replace_condition(m::Predicated(true));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5071 : : }
5072 : :
5073 : : /*----- Sort merge join does not introduce SIMD. -----*/
5074 [ # # # # : 0 : post_cond.add_condition(NoSIMD());
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
5075 : :
5076 : 0 : Sortedness::order_t orders;
5077 : : if constexpr (not SortLeft) {
5078 [ # # # # : 0 : Sortedness sorting_left(post_cond_children[0].get().get_condition<Sortedness>());
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5079 [ # # # # : 0 : orders.merge(sorting_left.orders()); // preserve sortedness of left child (including order)
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5080 : 0 : }
5081 : : if constexpr (not SortRight) {
5082 [ # # # # : 0 : Sortedness sorting_right(post_cond_children[1].get().get_condition<Sortedness>());
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5083 [ # # # # : 0 : orders.merge(sorting_right.orders()); // preserve sortedness of right child (including order)
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5084 : 0 : }
5085 : : if constexpr (SortLeft or SortRight) {
5086 : : /*----- Decompose each clause of the join predicate of the form `A.x = B.y` into parts `A.x` and `B.y`. -----*/
5087 [ # # # # : 0 : auto [keys_parent, keys_child] = decompose_equi_predicate(M.join.predicate(), M.parent.schema());
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5088 : :
5089 : : /*----- Sort merge join does sort the data on the respective key. -----*/
5090 : : if constexpr (SortLeft) {
5091 [ # # # # : 0 : for (auto &key_parent : keys_parent) {
# # # # #
# # # # #
# # ]
5092 [ # # # # : 0 : if (orders.find(key_parent) == orders.cend())
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
5093 [ # # # # : 0 : orders.add(key_parent, Sortedness::O_ASC); // add sortedness for left child
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5094 : : }
5095 : : }
5096 : : if constexpr (SortRight) {
5097 [ # # # # : 0 : for (auto &key_child : keys_child) {
# # # # #
# # # # #
# # ]
5098 [ # # # # : 0 : if (orders.find(key_child) == orders.cend())
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
5099 [ # # # # : 0 : orders.add(key_child, Sortedness::O_ASC); // add sortedness for right child
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5100 : : }
5101 : : }
5102 : 0 : }
5103 [ # # # # : 0 : post_cond.add_condition(Sortedness(std::move(orders)));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
5104 : :
5105 : 0 : return post_cond;
5106 [ # # # # : 0 : }
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5107 : :
5108 : : template<bool SortLeft, bool SortRight, bool Predicated, bool CmpPredicated>
5109 : 0 : double SortMergeJoin<SortLeft, SortRight, Predicated, CmpPredicated>::cost(const Match<SortMergeJoin> &M)
5110 : : {
5111 : 0 : const double card_left = M.parent.info().estimated_cardinality;
5112 : 0 : const double card_right = M.child.info().estimated_cardinality;
5113 : :
5114 : 0 : double cost = card_left + card_right; // cost for merge
5115 : : if constexpr (SortLeft)
5116 : 0 : cost += std::log2(card_left) * card_left; // cost for sort left
5117 : : if constexpr (SortRight)
5118 : 0 : cost += std::log2(card_right) * card_right; // cost for sort right
5119 : :
5120 : 0 : return cost;
5121 : : }
5122 : :
5123 : : template<bool SortLeft, bool SortRight, bool Predicated, bool CmpPredicated>
5124 : 0 : void SortMergeJoin<SortLeft, SortRight, Predicated, CmpPredicated>::execute(
5125 : : const Match<SortMergeJoin> &M,
5126 : : setup_t setup,
5127 : : pipeline_t pipeline,
5128 : : teardown_t teardown)
5129 : : {
5130 : 0 : auto &env = CodeGenContext::Get().env();
5131 [ # # # # : 0 : const bool needs_buffer_parent = not is<const ScanOperator>(M.parent) or SortLeft;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5132 [ # # # # : 0 : const bool needs_buffer_child = not is<const ScanOperator>(M.child) or SortRight;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5133 : :
5134 : : /*----- Create infinite buffers to materialize the current results (if necessary). -----*/
5135 : 0 : M_insist(bool(M.left_materializing_factory),
5136 : : "`wasm::SortMergeJoin` must have a factory for the materialized left child");
5137 : 0 : M_insist(bool(M.right_materializing_factory),
5138 : : "`wasm::SortMergeJoin` must have a factory for the materialized right child");
5139 [ # # # # : 0 : const auto schema_parent = M.parent.schema().drop_constants().deduplicate();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5140 [ # # # # : 0 : const auto schema_child = M.child.schema().drop_constants().deduplicate();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
5141 : 0 : std::optional<GlobalBuffer> buffer_parent, buffer_child;
5142 [ # # # # : 0 : if (needs_buffer_parent)
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5143 [ # # # # : 0 : buffer_parent.emplace(schema_parent, *M.left_materializing_factory);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5144 [ # # # # : 0 : if (needs_buffer_child)
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5145 [ # # # # : 0 : buffer_child.emplace(schema_child, *M.right_materializing_factory);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5146 : :
5147 : : /*----- Create child functions. -----*/
5148 [ # # # # : 0 : if (needs_buffer_parent) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5149 [ # # # # : 0 : FUNCTION(sort_merge_join_parent_pipeline, void(void)) // create function for parent pipeline
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
5150 : : {
5151 [ # # # # : 0 : auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
5152 [ # # # # : 0 : M.children[0]->execute(
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5153 [ # # # # : 0 : /* setup= */ setup_t::Make_Without_Parent([&](){ buffer_parent->setup(); }),
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5154 : 0 : /* pipeline= */ [&](){ buffer_parent->consume(); },
5155 [ # # # # : 0 : /* teardown= */ teardown_t::Make_Without_Parent([&](){ buffer_parent->teardown(); })
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5156 : : );
5157 : 0 : }
5158 [ # # # # : 0 : sort_merge_join_parent_pipeline(); // call parent function
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5159 : 0 : }
5160 [ # # # # : 0 : if (needs_buffer_child) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5161 [ # # # # : 0 : FUNCTION(sort_merge_join_child_pipeline, void(void)) // create function for child pipeline
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
5162 : : {
5163 [ # # # # : 0 : auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
5164 [ # # # # : 0 : M.children[1]->execute(
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5165 [ # # # # : 0 : /* setup= */ setup_t::Make_Without_Parent([&](){ buffer_child->setup(); }),
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5166 : 0 : /* pipeline= */ [&](){ buffer_child->consume(); },
5167 [ # # # # : 0 : /* teardown= */ teardown_t::Make_Without_Parent([&](){ buffer_child->teardown(); })
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5168 : : );
5169 : 0 : }
5170 [ # # # # : 0 : sort_merge_join_child_pipeline(); // call child function
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5171 : 0 : }
5172 : :
5173 : : /*----- Decompose each clause of the join predicate of the form `A.x = B.y` into parts `A.x` and `B.y`. -----*/
5174 : 0 : std::vector<SortingOperator::order_type> order_parent, order_child;
5175 [ # # # # : 0 : for (auto &clause : M.join.predicate()) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
5176 [ # # # # : 0 : M_insist(clause.size() == 1, "invalid equi-predicate");
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5177 : 0 : auto &literal = clause[0];
5178 [ # # # # : 0 : auto &binary = as<const BinaryExpr>(literal.expr());
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
5179 [ # # # # : 0 : M_insist((not literal.negative() and binary.tok == TK_EQUAL) or
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
5180 : : (literal.negative() and binary.tok == TK_BANG_EQUAL), "invalid equi-predicate");
5181 [ # # # # : 0 : M_insist(is<const Designator>(binary.lhs), "invalid equi-predicate");
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
5182 [ # # # # : 0 : M_insist(is<const Designator>(binary.rhs), "invalid equi-predicate");
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
5183 [ # # # # : 0 : auto [expr_parent, expr_child] = M.parent.schema().has(Schema::Identifier(*binary.lhs)) ?
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
5184 [ # # # # : 0 : std::make_pair(binary.lhs.get(), binary.rhs.get()) : std::make_pair(binary.rhs.get(), binary.lhs.get());
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
5185 [ # # # # : 0 : order_parent.emplace_back(*expr_parent, true); // ascending order
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5186 [ # # # # : 0 : order_child.emplace_back(*expr_child, true); // ascending order
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5187 : : }
5188 [ # # # # : 0 : M_insist(order_parent.size() == order_child.size(), "number of found IDs differ");
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5189 [ # # # # : 0 : M_insist(not order_parent.empty(), "must find at least one ID");
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5190 : :
5191 : : /*----- If necessary, invoke sorting algorithm with buffer to sort. -----*/
5192 : : if constexpr (SortLeft)
5193 [ # # # # : 0 : quicksort<CmpPredicated>(*buffer_parent, order_parent);
# # # # #
# # # # #
# # ]
5194 : : if constexpr (SortRight)
5195 [ # # # # : 0 : quicksort<CmpPredicated>(*buffer_child, order_child);
# # # # #
# # # # #
# # ]
5196 : :
5197 : : /*----- Create predicate to check if child co-group is smaller or equal than the one of the parent relation. -----*/
5198 : 0 : auto child_smaller_equal = [&]() -> Boolx1 {
5199 : 0 : std::optional<Boolx1> child_smaller_equal_;
5200 [ # # # # : 0 : for (std::size_t i = 0; i < order_child.size(); ++i) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5201 [ # # # # : 0 : auto &des_parent = as<const Designator>(order_parent[i].first);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5202 [ # # # # : 0 : auto &des_child = as<const Designator>(order_child[i].first);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5203 [ # # # # : 0 : Token leq = Token::CreateArtificial(TK_LESS_EQUAL);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5204 [ # # # # : 0 : auto cpy_parent = std::make_unique<Designator>(des_parent.tok, des_parent.table_name, des_parent.attr_name,
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5205 [ # # # # : 0 : des_parent.type(), des_parent.target());
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
5206 [ # # # # : 0 : auto cpy_child = std::make_unique<Designator>(des_child.tok, des_child.table_name, des_child.attr_name,
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5207 [ # # # # : 0 : des_child.type(), des_child.target());
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
5208 [ # # # # : 0 : BinaryExpr expr(std::move(leq), std::move(cpy_child), std::move(cpy_parent));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
5209 : :
5210 [ # # # # : 0 : auto child = env.get(Schema::Identifier(des_child));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
5211 [ # # # # : 0 : Boolx1 cmp = env.compile<_Boolx1>(expr).is_true_and_not_null();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
5212 [ # # # # : 0 : if (child_smaller_equal_)
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5213 [ # # # # : 0 : child_smaller_equal_.emplace(*child_smaller_equal_ and (is_null(child) or cmp));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
5214 : : else
5215 [ # # # # : 0 : child_smaller_equal_.emplace(is_null(child) or cmp);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
5216 : 0 : }
5217 [ # # # # : 0 : M_insist(bool(child_smaller_equal_));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5218 [ # # # # : 0 : return *child_smaller_equal_;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5219 : 0 : };
5220 : :
5221 : : /*----- Compile data layouts to generate sequential loads from buffers. -----*/
5222 [ # # # # : 0 : static Schema empty_schema;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
5223 [ # # # # : 0 : Var<U32x1> tuple_id_parent, tuple_id_child; // default initialized to 0
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
5224 [ # # # # : 0 : auto [inits_parent, loads_parent, _jumps_parent] = [&](){
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5225 [ # # # # : 0 : if (needs_buffer_parent) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5226 [ # # # # : 0 : return compile_load_sequential(buffer_parent->schema(), empty_schema, buffer_parent->base_address(),
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5227 [ # # # # : 0 : buffer_parent->layout(), 1, buffer_parent->schema(), tuple_id_parent);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
5228 : : } else {
5229 : 0 : auto &scan = as<const ScanOperator>(M.parent);
5230 [ # # # # : 0 : return compile_load_sequential(schema_parent, empty_schema, get_base_address(scan.store().table().name()),
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5231 [ # # # # : 0 : scan.store().table().layout(), 1, scan.store().table().schema(scan.alias()),
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
5232 : 0 : tuple_id_parent);
5233 : : }
5234 : 0 : }();
5235 [ # # # # : 0 : auto [inits_child, loads_child, _jumps_child] = [&](){
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5236 [ # # # # : 0 : if (needs_buffer_child) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5237 [ # # # # : 0 : return compile_load_sequential(buffer_child->schema(), empty_schema, buffer_child->base_address(),
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5238 [ # # # # : 0 : buffer_child->layout(), 1, buffer_child->schema(), tuple_id_child);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
5239 : : } else {
5240 : 0 : auto &scan = as<const ScanOperator>(M.child);
5241 [ # # # # : 0 : return compile_load_sequential(schema_child, empty_schema, get_base_address(scan.store().table().name()),
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5242 [ # # # # : 0 : scan.store().table().layout(), 1, scan.store().table().schema(scan.alias()),
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
5243 : 0 : tuple_id_child);
5244 : : }
5245 : 0 : }();
5246 : : /* since structured bindings cannot be used in lambda capture */
5247 [ # # # # : 0 : Block jumps_parent(std::move(_jumps_parent)), jumps_child(std::move(_jumps_child));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
5248 : :
5249 : : /*----- Process both buffers together. -----*/
5250 [ # # # # : 0 : setup();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5251 [ # # # # : 0 : inits_parent.attach_to_current();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5252 [ # # # # : 0 : inits_child.attach_to_current();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5253 [ # # # # : 0 : U32x1 size_parent = needs_buffer_parent ? buffer_parent->size()
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
5254 [ # # # # : 0 : : get_num_rows(as<const ScanOperator>(M.parent).store().table().name());
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
5255 [ # # # # : 0 : U32x1 size_child = needs_buffer_child ? buffer_child->size()
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
5256 [ # # # # : 0 : : get_num_rows(as<const ScanOperator>(M.child).store().table().name());
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
5257 [ # # # # : 0 : WHILE (tuple_id_parent < size_parent and tuple_id_child < size_child) { // neither end reached
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5258 [ # # # # : 0 : loads_parent.attach_to_current();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5259 [ # # # # : 0 : loads_child.attach_to_current();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5260 : : if constexpr (Predicated) {
5261 [ # # # # : 0 : env.add_predicate(M.join.predicate());
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5262 [ # # # # : 0 : pipeline();
# # # # #
# # # # #
# # ]
5263 : : } else {
5264 [ # # # # : 0 : M_insist(CodeGenContext::Get().num_simd_lanes() == 1, "invalid number of SIMD lanes");
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
5265 [ # # # # : 0 : IF (env.compile<_Boolx1>(M.join.predicate()).is_true_and_not_null()) { // predicate fulfilled
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
5266 : 0 : pipeline();
5267 : 0 : };
5268 : : }
5269 [ # # # # : 0 : IF (child_smaller_equal()) {
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
5270 : 0 : jumps_child.attach_to_current();
5271 : 0 : } ELSE {
5272 : 0 : jumps_parent.attach_to_current();
5273 : 0 : };
5274 : : }
5275 [ # # # # : 0 : teardown();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
5276 : 0 : }
5277 : :
5278 : :
5279 : : /*======================================================================================================================
5280 : : * Limit
5281 : : *====================================================================================================================*/
5282 : :
5283 : 0 : ConditionSet Limit::pre_condition(std::size_t child_idx, const std::tuple<const LimitOperator*>&)
5284 : : {
5285 : 0 : M_insist(child_idx == 0);
5286 : :
5287 : 0 : ConditionSet pre_cond;
5288 : :
5289 : : /*----- Limit does not support SIMD. -----*/
5290 [ # # # # ]: 0 : pre_cond.add_condition(NoSIMD());
5291 : :
5292 : 0 : return pre_cond;
5293 [ # # ]: 0 : }
5294 : :
5295 : 0 : void Limit::execute(const Match<Limit> &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
5296 : : {
5297 : 0 : std::optional<Block> teardown_block; ///< block around pipeline code to jump to teardown code when limit is reached
5298 : 0 : std::optional<BlockUser> use_teardown; ///< block user to set teardown block active
5299 : :
5300 : 0 : std::optional<Var<U32x1>> counter; ///< variable to *locally* count
5301 : : /* default initialized to 0 */
5302 [ # # ]: 0 : Global<U32x1> counter_backup; ///< *global* counter backup since the following code may be called multiple times
5303 : :
5304 [ # # ]: 0 : M.child->execute(
5305 [ # # # # ]: 0 : /* setup= */ setup_t(std::move(setup), [&](){
5306 : 0 : counter.emplace(counter_backup);
5307 : 0 : teardown_block.emplace("limit.teardown", true); // create block
5308 : 0 : use_teardown.emplace(*teardown_block); // set block active s.t. it contains all following pipeline code
5309 : 0 : }),
5310 [ # # ]: 0 : /* pipeline= */ [&, pipeline=std::move(pipeline)](){
5311 : 0 : M_insist(bool(teardown_block));
5312 : 0 : M_insist(bool(counter));
5313 : 0 : const uint32_t limit = M.limit.offset() + M.limit.limit();
5314 : :
5315 : : /*----- Abort pipeline, i.e. go to teardown code, if limit is exceeded. -----*/
5316 [ # # ]: 0 : IF (*counter >= limit) {
5317 : 0 : GOTO(*teardown_block);
5318 : 0 : };
5319 : :
5320 : : /*----- Emit result if in bounds. -----*/
5321 [ # # ]: 0 : if (M.limit.offset()) {
5322 [ # # # # ]: 0 : IF (*counter >= uint32_t(M.limit.offset())) {
5323 [ # # ]: 0 : Wasm_insist(*counter < limit, "counter must not exceed limit");
5324 : 0 : pipeline();
5325 : 0 : };
5326 : 0 : } else {
5327 [ # # ]: 0 : Wasm_insist(*counter < limit, "counter must not exceed limit");
5328 : 0 : pipeline();
5329 : : }
5330 : :
5331 : : /*----- Update counter. -----*/
5332 : 0 : *counter += 1U;
5333 : 0 : },
5334 [ # # # # ]: 0 : /* teardown= */ teardown_t::Make_Without_Parent([&, teardown=std::move(teardown)](){
5335 : 0 : M_insist(bool(teardown_block));
5336 : 0 : M_insist(bool(use_teardown));
5337 : 0 : use_teardown.reset(); // deactivate block
5338 : 0 : teardown_block.reset(); // emit block containing pipeline code into parent -> GOTO jumps here
5339 : 0 : teardown(); // *before* own teardown code to *not* jump over it in case of another limit operator
5340 : 0 : M_insist(bool(counter));
5341 : 0 : counter_backup = *counter;
5342 : 0 : counter.reset();
5343 : 0 : })
5344 : : );
5345 : 0 : }
5346 : :
5347 : :
5348 : : /*======================================================================================================================
5349 : : * Grouping combined with Join
5350 : : *====================================================================================================================*/
5351 : :
5352 : 0 : ConditionSet HashBasedGroupJoin::pre_condition(
5353 : : std::size_t child_idx,
5354 : : const std::tuple<const GroupingOperator*, const JoinOperator*, const Wildcard*, const Wildcard*>
5355 : : &partial_inner_nodes)
5356 : : {
5357 : 0 : ConditionSet pre_cond;
5358 : :
5359 : : /*----- Hash-based group-join can only be used if aggregates only depend on either build or probe relation. -----*/
5360 : 0 : auto &grouping = *std::get<0>(partial_inner_nodes);
5361 [ # # # # ]: 0 : for (auto &fn_expr : grouping.aggregates()) {
5362 [ # # ]: 0 : M_insist(fn_expr.get().args.size() <= 1);
5363 [ # # # # : 0 : if (fn_expr.get().args.size() == 1 and not is<const Designator>(fn_expr.get().args[0])) // XXX: expression with only designators from either child also valid
# # ]
5364 [ # # ]: 0 : return ConditionSet::Make_Unsatisfiable();
5365 : : }
5366 : :
5367 : : /*----- Hash-based group-join can only be used for binary joins on equi-predicates. -----*/
5368 : 0 : auto &join = *std::get<1>(partial_inner_nodes);
5369 [ # # # # : 0 : if (not join.predicate().is_equi())
# # ]
5370 [ # # ]: 0 : return ConditionSet::Make_Unsatisfiable();
5371 : :
5372 [ # # ]: 0 : M_insist(child_idx < 2);
5373 [ # # ]: 0 : if (child_idx == 0) {
5374 : : /*----- Decompose each clause of the join predicate of the form `A.x = B.y` into parts `A.x` and `B.y`. -----*/
5375 : 0 : auto &build = *std::get<2>(partial_inner_nodes);
5376 [ # # # # : 0 : const auto build_keys = decompose_equi_predicate(join.predicate(), build.schema()).first;
# # ]
5377 : :
5378 : : /*----- Hash-based group-join can only be used if grouping and join (i.e. build) key match (ignoring order). -*/
5379 [ # # ]: 0 : const auto num_grouping_keys = grouping.group_by().size();
5380 [ # # ]: 0 : if (num_grouping_keys != build_keys.size()) // XXX: duplicated IDs are still a match but rejected here
5381 [ # # ]: 0 : return ConditionSet::Make_Unsatisfiable();
5382 [ # # ]: 0 : for (std::size_t i = 0; i < num_grouping_keys; ++i) {
5383 [ # # # # ]: 0 : Schema::Identifier grouping_key(grouping.group_by()[i].first.get());
5384 [ # # # # ]: 0 : if (not contains(build_keys, grouping_key))
5385 [ # # ]: 0 : return ConditionSet::Make_Unsatisfiable();
5386 [ # # ]: 0 : }
5387 [ # # ]: 0 : }
5388 : :
5389 : : /*----- Hash-based group-join does not support SIMD. -----*/
5390 [ # # # # ]: 0 : pre_cond.add_condition(NoSIMD());
5391 : :
5392 : 0 : return pre_cond;
5393 : 0 : }
5394 : :
5395 : 0 : double HashBasedGroupJoin::cost(const Match<HashBasedGroupJoin> &M)
5396 : : {
5397 : 0 : return 1.5 * M.build.info().estimated_cardinality + 1.0 * M.probe.info().estimated_cardinality +
5398 : 0 : 1.0 * M.join.info().estimated_cardinality;
5399 : : }
5400 : :
5401 : 0 : ConditionSet HashBasedGroupJoin::post_condition(const Match<HashBasedGroupJoin>&)
5402 : : {
5403 : 0 : ConditionSet post_cond;
5404 : :
5405 : : /*----- Hash-based group-join does not introduce predication (it is already handled by the hash table). -----*/
5406 [ # # # # ]: 0 : post_cond.add_condition(Predicated(false));
5407 : :
5408 : : /*----- Hash-based group-join does not introduce SIMD. -----*/
5409 [ # # # # ]: 0 : post_cond.add_condition(NoSIMD());
5410 : :
5411 : 0 : return post_cond;
5412 [ # # ]: 0 : }
5413 : :
5414 : 0 : void HashBasedGroupJoin::execute(const Match<HashBasedGroupJoin> &M, setup_t setup, pipeline_t pipeline,
5415 : : teardown_t teardown)
5416 : : {
5417 : : // TODO: determine setup
5418 : 0 : const uint64_t AGGREGATES_SIZE_THRESHOLD_IN_BITS =
5419 : 0 : M.use_in_place_values ? std::numeric_limits<uint64_t>::max() : 0;
5420 : :
5421 : 0 : auto &C = Catalog::Get();
5422 : 0 : const auto num_keys = M.grouping.group_by().size();
5423 : :
5424 : : /*----- Compute hash table schema and information about aggregates, especially AVG aggregates. -----*/
5425 : 0 : Schema ht_schema;
5426 [ # # ]: 0 : for (std::size_t i = 0; i < num_keys; ++i) {
5427 [ # # # # ]: 0 : auto &e = M.grouping.schema()[i];
5428 [ # # # # ]: 0 : ht_schema.add(e.id, e.type, e.constraints);
5429 : 0 : }
5430 [ # # # # : 0 : auto aggregates_info = compute_aggregate_info(M.grouping.aggregates(), M.grouping.schema(), num_keys);
# # ]
5431 : 0 : const auto &aggregates = aggregates_info.first;
5432 : 0 : const auto &avg_aggregates = aggregates_info.second;
5433 : 0 : bool needs_build_counter = false; ///< flag whether additional COUNT per group during build phase must be emitted
5434 : 0 : uint64_t aggregates_size_in_bits = 0;
5435 [ # # ]: 0 : for (auto &info : aggregates) {
5436 [ # # # # ]: 0 : ht_schema.add(info.entry);
5437 [ # # ]: 0 : aggregates_size_in_bits += info.entry.type->size();
5438 : :
5439 : : /* Add additional COUNT per group during build phase if COUNT or SUM dependent on probe relation occurs. */
5440 [ # # # # ]: 0 : if (info.fnid == m::Function::FN_COUNT or info.fnid == m::Function::FN_SUM) {
5441 [ # # ]: 0 : if (not info.args.empty()) {
5442 [ # # ]: 0 : M_insist(info.args.size() == 1, "aggregate functions expect at most one argument");
5443 [ # # ]: 0 : auto &des = as<const Designator>(*info.args[0]);
5444 [ # # # # : 0 : Schema::Identifier arg(des.table_name.text, des.attr_name.text.assert_not_none());
# # ]
5445 [ # # # # : 0 : if (M.probe.schema().has(arg))
# # ]
5446 : 0 : needs_build_counter = true;
5447 : 0 : }
5448 : 0 : }
5449 : : }
5450 [ # # ]: 0 : if (needs_build_counter) {
5451 [ # # # # : 0 : ht_schema.add(Schema::Identifier(C.pool("$build_counter")), Type::Get_Integer(Type::TY_Scalar, 8),
# # # # #
# ]
5452 : : Schema::entry_type::NOT_NULLABLE);
5453 : 0 : aggregates_size_in_bits += 64;
5454 : 0 : }
5455 [ # # # # : 0 : ht_schema.add(Schema::Identifier(C.pool("$probe_counter")), Type::Get_Integer(Type::TY_Scalar, 8),
# # # # #
# ]
5456 : : Schema::entry_type::NOT_NULLABLE);
5457 : 0 : aggregates_size_in_bits += 64;
5458 : :
5459 : : /*----- Decompose each clause of the join predicate of the form `A.x = B.y` into parts `A.x` and `B.y`. -----*/
5460 [ # # # # : 0 : const auto [build_keys, probe_keys] = decompose_equi_predicate(M.join.predicate(), M.build.schema());
# # ]
5461 [ # # ]: 0 : M_insist(build_keys.size() == num_keys);
5462 : :
5463 : : /*----- Compute initial capacity of hash table. -----*/
5464 [ # # ]: 0 : uint32_t initial_capacity = compute_initial_ht_capacity(M.grouping, M.load_factor);
5465 : :
5466 : : /*----- Create hash table for build relation. -----*/
5467 : 0 : std::unique_ptr<HashTable> ht;
5468 [ # # ]: 0 : std::vector<HashTable::index_t> key_indices(num_keys);
5469 [ # # ]: 0 : std::iota(key_indices.begin(), key_indices.end(), 0);
5470 [ # # ]: 0 : if (M.use_open_addressing_hashing) {
5471 [ # # ]: 0 : if (aggregates_size_in_bits < AGGREGATES_SIZE_THRESHOLD_IN_BITS)
5472 [ # # ]: 0 : ht = std::make_unique<GlobalOpenAddressingInPlaceHashTable>(ht_schema, std::move(key_indices),
5473 : : initial_capacity);
5474 : : else
5475 [ # # ]: 0 : ht = std::make_unique<GlobalOpenAddressingOutOfPlaceHashTable>(ht_schema, std::move(key_indices),
5476 : : initial_capacity);
5477 [ # # ]: 0 : if (M.use_quadratic_probing)
5478 [ # # # # ]: 0 : as<OpenAddressingHashTableBase>(*ht).set_probing_strategy<QuadraticProbing>();
5479 : : else
5480 [ # # # # ]: 0 : as<OpenAddressingHashTableBase>(*ht).set_probing_strategy<LinearProbing>();
5481 : 0 : } else {
5482 [ # # ]: 0 : ht = std::make_unique<GlobalChainedHashTable>(ht_schema, std::move(key_indices), initial_capacity);
5483 : : }
5484 : :
5485 : 0 : std::optional<HashTable::entry_t> dummy; ///< *local* dummy slot
5486 : :
5487 : : /** Helper function to compute aggregates to be stored in \p entry given the arguments contained in environment \p
5488 : : * env for the phase (i.e. build or probe) with the schema \p schema. The flag \p build_phase determines which
5489 : : * phase is currently active.
5490 : : *
5491 : : * Returns three code blocks: the first one initializes all aggregates, the second one updates all but the AVG
5492 : : * aggregates, and the third one updates the AVG aggregates. */
5493 : 0 : auto compile_aggregates = [&](HashTable::entry_t &entry, const Environment &env, const Schema &schema,
5494 : : bool build_phase) -> std::tuple<Block, Block, Block>
5495 : : {
5496 : 0 : Block init_aggs("hash_based_group_join.init_aggs", false),
5497 [ # # ]: 0 : update_aggs("hash_based_group_join.update_aggs", false),
5498 [ # # ]: 0 : update_avg_aggs("hash_based_group_join.update_avg_aggs", false);
5499 [ # # ]: 0 : for (auto &info : aggregates) {
5500 : 0 : bool is_min = false; ///< flag to indicate whether aggregate function is MIN
5501 [ # # # # : 0 : switch (info.fnid) {
# # ]
5502 : : default:
5503 [ # # ]: 0 : M_unreachable("unsupported aggregate function");
5504 : : case m::Function::FN_MIN:
5505 : 0 : is_min = true; // set flag and delegate to MAX case
5506 : : case m::Function::FN_MAX: {
5507 [ # # ]: 0 : M_insist(info.args.size() == 1, "MIN and MAX aggregate functions expect exactly one argument");
5508 [ # # ]: 0 : auto &arg = as<const Designator>(*info.args[0]);
5509 [ # # # # : 0 : const bool bound = schema.has(Schema::Identifier(arg.table_name.text,
# # ]
5510 [ # # ]: 0 : arg.attr_name.text.assert_not_none()));
5511 : :
5512 [ # # ]: 0 : std::visit(overloaded {
5513 : 0 : [&]<sql_type _T>(HashTable::reference_t<_T> &&r) -> void
5514 : : requires (not (std::same_as<_T, _Boolx1> or std::same_as<_T, NChar>)) {
5515 : : using type = typename _T::type;
5516 : : using T = PrimitiveExpr<type>;
5517 : :
5518 [ # # # # : 0 : if (build_phase) {
# # # # #
# # # ]
5519 : 0 : BLOCK_OPEN(init_aggs) {
5520 [ # # # # : 0 : auto neutral = is_min ? T(std::numeric_limits<type>::max())
# # # # #
# # # # #
# # # # #
# # # #
# ]
5521 [ # # # # : 0 : : T(std::numeric_limits<type>::lowest());
# # # # #
# # # ]
5522 [ # # # # : 0 : if (bound) {
# # # # #
# # # ]
5523 [ # # # # : 0 : auto _arg = env.compile(arg);
# # # # #
# # # ]
5524 [ # # # # : 0 : auto [val_, is_null] = convert<_T>(_arg).split();
# # # # #
# # # # #
# # # # #
# # # #
# ]
5525 [ # # # # : 0 : T val(val_); // due to structured binding and lambda closure
# # # # #
# # # ]
5526 [ # # # # : 0 : IF (is_null) {
# # # # #
# # # # #
# # # # #
# # # #
# ]
5527 [ # # # # : 0 : r.clone().set_value(neutral); // initialize with neutral element +inf or -inf
# # # # #
# # # # #
# # # # #
# # # #
# ]
5528 [ # # # # : 0 : if (info.entry.nullable())
# # # # #
# # # ]
5529 [ # # # # : 0 : r.clone().set_null_bit(Boolx1(true)); // first value is NULL
# # # # #
# # # # #
# # # # #
# # # #
# ]
5530 [ # # # # : 0 : } ELSE {
# # # # #
# # # ]
5531 [ # # # # : 0 : r.clone().set_value(val); // initialize with first value
# # # # #
# # # # #
# # # # #
# # # #
# ]
5532 [ # # # # : 0 : if (info.entry.nullable())
# # # # #
# # # ]
5533 [ # # # # : 0 : r.clone().set_null_bit(Boolx1(false)); // first value is not NULL
# # # # #
# # # # #
# # # # #
# # # #
# ]
5534 : 0 : };
5535 : 0 : } else {
5536 [ # # # # : 0 : r.clone().set_value(neutral); // initialize with neutral element +inf or -inf
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
5537 [ # # # # : 0 : if (info.entry.nullable())
# # # # #
# # # ]
5538 [ # # # # : 0 : r.clone().set_null_bit(Boolx1(true)); // initialize with neutral element NULL
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
5539 : : }
5540 : 0 : }
5541 : 0 : }
5542 [ # # # # : 0 : if (not bound) {
# # # # #
# # # ]
5543 : 0 : r.discard();
5544 : 0 : return; // MIN and MAX does not change in phase when argument is unbound
5545 : : }
5546 : 0 : BLOCK_OPEN(update_aggs) {
5547 [ # # # # : 0 : auto _arg = env.compile(arg);
# # # # #
# # # ]
5548 [ # # # # : 0 : _T _new_val = convert<_T>(_arg);
# # # # #
# # # ]
5549 [ # # # # : 0 : if (_new_val.can_be_null()) {
# # # # #
# # # ]
5550 [ # # # # : 0 : auto [new_val_, new_val_is_null_] = _new_val.split();
# # # # #
# # # ]
5551 [ # # # # : 0 : auto [old_min_max_, old_min_max_is_null] = _T(r.clone()).split();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
5552 [ # # # # : 0 : const Var<Boolx1> new_val_is_null(new_val_is_null_); // due to multiple uses
# # # # #
# # # ]
5553 : :
5554 [ # # # # : 0 : auto chosen_r = Select(new_val_is_null, dummy->extract<_T>(info.entry.id), r.clone());
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
5555 : : if constexpr (std::floating_point<type>) {
5556 [ # # # # : 0 : chosen_r.set_value(
# # # # #
# # # # #
# # # # #
# ]
5557 [ # # # # : 0 : is_min ? min(old_min_max_, new_val_) // update old min with new value
# # # # ]
5558 [ # # # # ]: 0 : : max(old_min_max_, new_val_) // update old max with new value
5559 : : ); // if new value is NULL, only dummy is written
5560 : : } else {
5561 [ # # # # : 0 : const Var<T> new_val(new_val_),
# # # # ]
5562 [ # # # # : 0 : old_min_max(old_min_max_); // due to multiple uses
# # # # ]
5563 [ # # # # : 0 : auto cmp = is_min ? new_val < old_min_max : new_val > old_min_max;
# # # # #
# # # # #
# # # # #
# # # #
# ]
5564 [ # # # # : 0 : chosen_r.set_value(
# # # # ]
5565 [ # # # # : 0 : Select(cmp,
# # # # ]
5566 : : new_val, // update to new value
5567 : : old_min_max) // do not update
5568 : : ); // if new value is NULL, only dummy is written
5569 : 0 : }
5570 [ # # # # : 0 : r.set_null_bit(
# # # # #
# # # ]
5571 [ # # # # : 0 : old_min_max_is_null and new_val_is_null // MIN/MAX is NULL iff all values are NULL
# # # # #
# # # ]
5572 : : );
5573 : 0 : } else {
5574 [ # # # # : 0 : auto new_val_ = _new_val.insist_not_null();
# # # # #
# # # ]
5575 [ # # # # : 0 : auto old_min_max_ = _T(r.clone()).insist_not_null();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
5576 : : if constexpr (std::floating_point<type>) {
5577 [ # # # # : 0 : r.set_value(
# # # # #
# # # # #
# # # # #
# ]
5578 [ # # # # : 0 : is_min ? min(old_min_max_, new_val_) // update old min with new value
# # # # ]
5579 [ # # # # ]: 0 : : max(old_min_max_, new_val_) // update old max with new value
5580 : : );
5581 : : } else {
5582 [ # # # # : 0 : const Var<T> new_val(new_val_),
# # # # ]
5583 [ # # # # : 0 : old_min_max(old_min_max_); // due to multiple uses
# # # # ]
5584 [ # # # # : 0 : auto cmp = is_min ? new_val < old_min_max : new_val > old_min_max;
# # # # #
# # # # #
# # # # #
# # # #
# ]
5585 [ # # # # : 0 : r.set_value(
# # # # ]
5586 [ # # # # : 0 : Select(cmp,
# # # # ]
5587 : : new_val, // update to new value
5588 : : old_min_max) // do not update
5589 : : );
5590 : 0 : }
5591 : : /* do not update NULL bit since it is already set to `false` */
5592 : 0 : }
5593 : 0 : }
5594 : 0 : },
5595 : 0 : []<sql_type _T>(HashTable::reference_t<_T>&&) -> void
5596 : : requires std::same_as<_T,_Boolx1> or std::same_as<_T, NChar> {
5597 : 0 : M_unreachable("invalid type");
5598 : : },
5599 : 0 : [](std::monostate) -> void { M_unreachable("invalid reference"); },
5600 [ # # ]: 0 : }, entry.extract(info.entry.id));
5601 : 0 : break;
5602 : : }
5603 : : case m::Function::FN_AVG: {
5604 [ # # ]: 0 : auto it = avg_aggregates.find(info.entry.id);
5605 [ # # ]: 0 : M_insist(it != avg_aggregates.end());
5606 : 0 : const auto &avg_info = it->second;
5607 [ # # ]: 0 : M_insist(avg_info.compute_running_avg,
5608 : : "AVG aggregate may only occur for running average computations");
5609 [ # # ]: 0 : M_insist(info.args.size() == 1, "AVG aggregate function expects exactly one argument");
5610 [ # # ]: 0 : auto &arg = as<const Designator>(*info.args[0]);
5611 [ # # # # : 0 : const bool bound = schema.has(Schema::Identifier(arg.table_name.text,
# # ]
5612 [ # # ]: 0 : arg.attr_name.text.assert_not_none()));
5613 : :
5614 [ # # ]: 0 : auto r = entry.extract<_Doublex1>(info.entry.id);
5615 : :
5616 [ # # ]: 0 : if (build_phase) {
5617 [ # # ]: 0 : BLOCK_OPEN(init_aggs) {
5618 [ # # ]: 0 : if (bound) {
5619 [ # # ]: 0 : auto _arg = env.compile(arg);
5620 [ # # # # ]: 0 : auto [val_, is_null] = convert<_Doublex1>(_arg).split();
5621 [ # # ]: 0 : Doublex1 val(val_); // due to structured binding and lambda closure
5622 [ # # ]: 0 : IF (is_null) {
5623 [ # # # # ]: 0 : r.clone().set_value(Doublex1(0.0)); // initialize with neutral element 0
5624 [ # # ]: 0 : if (info.entry.nullable())
5625 [ # # # # ]: 0 : r.clone().set_null_bit(Boolx1(true)); // first value is NULL
5626 [ # # ]: 0 : } ELSE {
5627 [ # # # # ]: 0 : r.clone().set_value(val); // initialize with first value
5628 [ # # ]: 0 : if (info.entry.nullable())
5629 [ # # # # ]: 0 : r.clone().set_null_bit(Boolx1(false)); // first value is not NULL
5630 : 0 : };
5631 : 0 : } else {
5632 [ # # # # : 0 : r.clone().set_value(Doublex1(0.0)); // initialize with neutral element 0
# # ]
5633 [ # # ]: 0 : if (info.entry.nullable())
5634 [ # # # # : 0 : r.clone().set_null_bit(Boolx1(true)); // initialize with neutral element NULL
# # ]
5635 : : }
5636 : : }
5637 : 0 : }
5638 [ # # ]: 0 : if (not bound) {
5639 [ # # ]: 0 : r.discard();
5640 : 0 : break; // AVG does not change in phase when argument is unbound
5641 : : }
5642 [ # # ]: 0 : BLOCK_OPEN(update_avg_aggs) {
5643 : : /* Compute AVG as iterative mean as described in Knuth, The Art of Computer Programming
5644 : : * Vol 2, section 4.2.2. */
5645 [ # # ]: 0 : auto _arg = env.compile(arg);
5646 [ # # ]: 0 : _Doublex1 _new_val = convert<_Doublex1>(_arg);
5647 [ # # ]: 0 : if (_new_val.can_be_null()) {
5648 [ # # ]: 0 : auto [new_val, new_val_is_null_] = _new_val.split();
5649 [ # # # # : 0 : auto [old_avg_, old_avg_is_null] = _Doublex1(r.clone()).split();
# # ]
5650 [ # # ]: 0 : const Var<Boolx1> new_val_is_null(new_val_is_null_); // due to multiple uses
5651 [ # # ]: 0 : const Var<Doublex1> old_avg(old_avg_); // due to multiple uses
5652 : :
5653 [ # # ]: 0 : auto delta_absolute = new_val - old_avg;
5654 [ # # # # : 0 : auto running_count = _I64x1(entry.get<_I64x1>(avg_info.running_count)).insist_not_null();
# # ]
5655 [ # # # # ]: 0 : auto delta_relative = delta_absolute / running_count.to<double>();
5656 : :
5657 [ # # # # : 0 : auto chosen_r = Select(new_val_is_null, dummy->extract<_Doublex1>(info.entry.id), r.clone());
# # # # ]
5658 [ # # ]: 0 : chosen_r.set_value(
5659 [ # # ]: 0 : old_avg + delta_relative // update old average with new value
5660 : : ); // if new value is NULL, only dummy is written
5661 [ # # ]: 0 : r.set_null_bit(
5662 [ # # ]: 0 : old_avg_is_null and new_val_is_null // AVG is NULL iff all values are NULL
5663 : : );
5664 : 0 : } else {
5665 [ # # ]: 0 : auto new_val = _new_val.insist_not_null();
5666 [ # # # # : 0 : auto old_avg_ = _Doublex1(r.clone()).insist_not_null();
# # ]
5667 [ # # ]: 0 : const Var<Doublex1> old_avg(old_avg_); // due to multiple uses
5668 : :
5669 [ # # ]: 0 : auto delta_absolute = new_val - old_avg;
5670 [ # # # # : 0 : auto running_count = _I64x1(entry.get<_I64x1>(avg_info.running_count)).insist_not_null();
# # ]
5671 [ # # # # ]: 0 : auto delta_relative = delta_absolute / running_count.to<double>();
5672 [ # # ]: 0 : r.set_value(
5673 [ # # ]: 0 : old_avg + delta_relative // update old average with new value
5674 : : );
5675 : : /* do not update NULL bit since it is already set to `false` */
5676 : 0 : }
5677 : 0 : }
5678 : 0 : break;
5679 : 0 : }
5680 : : case m::Function::FN_SUM: {
5681 [ # # ]: 0 : M_insist(info.args.size() == 1, "SUM aggregate function expects exactly one argument");
5682 [ # # ]: 0 : auto &arg = as<const Designator>(*info.args[0]);
5683 [ # # # # : 0 : const bool bound = schema.has(Schema::Identifier(arg.table_name.text,
# # ]
5684 [ # # ]: 0 : arg.attr_name.text.assert_not_none()));
5685 : :
5686 [ # # ]: 0 : std::visit(overloaded {
5687 : 0 : [&]<sql_type _T>(HashTable::reference_t<_T> &&r) -> void
5688 : : requires (not (std::same_as<_T, _Boolx1> or std::same_as<_T, NChar>)) {
5689 : : using type = typename _T::type;
5690 : : using T = PrimitiveExpr<type>;
5691 : :
5692 [ # # # # : 0 : if (build_phase) {
# # # # #
# # # ]
5693 : 0 : BLOCK_OPEN(init_aggs) {
5694 [ # # # # : 0 : if (bound) {
# # # # #
# # # ]
5695 [ # # # # : 0 : auto _arg = env.compile(arg);
# # # # #
# # # ]
5696 [ # # # # : 0 : auto [val_, is_null] = convert<_T>(_arg).split();
# # # # #
# # # # #
# # # # #
# # # #
# ]
5697 [ # # # # : 0 : T val(val_); // due to structured binding and lambda closure
# # # # #
# # # ]
5698 [ # # # # : 0 : IF (is_null) {
# # # # #
# # # ]
5699 [ # # # # : 0 : r.clone().set_value(T(type(0))); // initialize with neutral element 0
# # # # #
# # # # #
# # # # #
# # # #
# ]
5700 [ # # # # : 0 : if (info.entry.nullable())
# # # # #
# # # ]
5701 [ # # # # : 0 : r.clone().set_null_bit(Boolx1(true)); // first value is NULL
# # # # #
# # # # #
# # # # #
# # # #
# ]
5702 [ # # # # : 0 : } ELSE {
# # # # #
# # # ]
5703 [ # # # # : 0 : r.clone().set_value(val); // initialize with first value
# # # # #
# # # # #
# # # # #
# # # #
# ]
5704 [ # # # # : 0 : if (info.entry.nullable())
# # # # #
# # # ]
5705 [ # # # # : 0 : r.clone().set_null_bit(Boolx1(false)); // first value is not NULL
# # # # #
# # # # #
# # # # #
# # # #
# ]
5706 : 0 : };
5707 : 0 : } else {
5708 [ # # # # : 0 : r.clone().set_value(T(type(0))); // initialize with neutral element 0
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
5709 [ # # # # : 0 : if (info.entry.nullable())
# # # # #
# # # ]
5710 [ # # # # : 0 : r.clone().set_null_bit(Boolx1(true)); // initialize with neutral element NULL
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
5711 : : }
5712 : : }
5713 : 0 : }
5714 [ # # # # : 0 : if (not bound) {
# # # # #
# # # ]
5715 : 0 : r.discard();
5716 : 0 : return; // SUM may later be multiplied with group counter but does not change here
5717 : : }
5718 : 0 : BLOCK_OPEN(update_aggs) {
5719 [ # # # # : 0 : auto _arg = env.compile(arg);
# # # # #
# # # ]
5720 [ # # # # : 0 : _T _new_val = convert<_T>(_arg);
# # # # #
# # # ]
5721 [ # # # # : 0 : if (_new_val.can_be_null()) {
# # # # #
# # # ]
5722 [ # # # # : 0 : auto [new_val, new_val_is_null_] = _new_val.split();
# # # # #
# # # ]
5723 [ # # # # : 0 : auto [old_sum, old_sum_is_null] = _T(r.clone()).split();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
5724 [ # # # # : 0 : const Var<Boolx1> new_val_is_null(new_val_is_null_); // due to multiple uses
# # # # #
# # # ]
5725 : :
5726 [ # # # # : 0 : auto chosen_r = Select(new_val_is_null, dummy->extract<_T>(info.entry.id), r.clone());
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
5727 [ # # # # : 0 : chosen_r.set_value(
# # # # #
# # # ]
5728 [ # # # # : 0 : old_sum + new_val // add new value to old sum
# # # # #
# # # # #
# # # # #
# # # #
# ]
5729 : : ); // if new value is NULL, only dummy is written
5730 [ # # # # : 0 : r.set_null_bit(
# # # # #
# # # ]
5731 [ # # # # : 0 : old_sum_is_null and new_val_is_null // SUM is NULL iff all values are NULL
# # # # #
# # # ]
5732 : : );
5733 : 0 : } else {
5734 [ # # # # : 0 : auto new_val = _new_val.insist_not_null();
# # # # #
# # # ]
5735 [ # # # # : 0 : auto old_sum = _T(r.clone()).insist_not_null();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
5736 [ # # # # : 0 : r.set_value(
# # # # #
# # # ]
5737 [ # # # # : 0 : old_sum + new_val // add new value to old sum
# # # # #
# # # # #
# # # # #
# # # #
# ]
5738 : : );
5739 : : /* do not update NULL bit since it is already set to `false` */
5740 : 0 : }
5741 : 0 : }
5742 : 0 : },
5743 : 0 : []<sql_type _T>(HashTable::reference_t<_T>&&) -> void
5744 : : requires std::same_as<_T,_Boolx1> or std::same_as<_T, NChar> {
5745 : 0 : M_unreachable("invalid type");
5746 : : },
5747 : 0 : [](std::monostate) -> void { M_unreachable("invalid reference"); },
5748 [ # # ]: 0 : }, entry.extract(info.entry.id));
5749 : 0 : break;
5750 : : }
5751 : : case m::Function::FN_COUNT: {
5752 [ # # ]: 0 : M_insist(info.args.size() <= 1, "COUNT aggregate function expects at most one argument");
5753 : :
5754 [ # # ]: 0 : auto r = entry.get<_I64x1>(info.entry.id); // do not extract to be able to access for AVG case
5755 : :
5756 [ # # ]: 0 : if (info.args.empty()) {
5757 [ # # ]: 0 : if (not build_phase) {
5758 [ # # ]: 0 : r.discard();
5759 : 0 : break; // COUNT(*) will later be multiplied with probe counter but only changes in build phase
5760 : : }
5761 [ # # ]: 0 : BLOCK_OPEN(init_aggs) {
5762 [ # # # # : 0 : r.clone() = _I64x1(1); // initialize with 1 (for first value)
# # ]
5763 : : }
5764 [ # # ]: 0 : BLOCK_OPEN(update_aggs) {
5765 [ # # # # : 0 : auto old_count = _I64x1(r.clone()).insist_not_null();
# # ]
5766 [ # # ]: 0 : r.set_value(
5767 [ # # ]: 0 : old_count + int64_t(1) // increment old count by 1
5768 : : );
5769 : : /* do not update NULL bit since it is already set to `false` */
5770 : 0 : }
5771 : 0 : } else {
5772 [ # # ]: 0 : auto &arg = as<const Designator>(*info.args[0]);
5773 [ # # # # : 0 : const bool bound = schema.has(Schema::Identifier(arg.table_name.text,
# # ]
5774 [ # # ]: 0 : arg.attr_name.text.assert_not_none()));
5775 : :
5776 [ # # ]: 0 : if (build_phase) {
5777 [ # # ]: 0 : BLOCK_OPEN(init_aggs) {
5778 [ # # ]: 0 : if (bound) {
5779 [ # # ]: 0 : auto _arg = env.compile(arg);
5780 : : I64x1 new_val_not_null =
5781 [ # # # # : 0 : can_be_null(_arg) ? not_null(_arg).to<int64_t>()
# # # # #
# # # ]
5782 [ # # # # ]: 0 : : (discard(_arg), I64x1(1)); // discard since no use
5783 [ # # # # : 0 : r.clone() = _I64x1(new_val_not_null); // initialize with 1 iff first value is present
# # # # ]
5784 : 0 : } else {
5785 [ # # # # : 0 : r.clone() = _I64x1(0); // initialize with neutral element 0
# # ]
5786 : : }
5787 : : }
5788 : 0 : }
5789 [ # # ]: 0 : if (not bound) {
5790 [ # # ]: 0 : r.discard();
5791 : 0 : break; // COUNT may later be multiplied with group counter but does not change here
5792 : : }
5793 [ # # ]: 0 : BLOCK_OPEN(update_aggs) {
5794 [ # # ]: 0 : auto _arg = env.compile(arg);
5795 : : I64x1 new_val_not_null =
5796 [ # # # # : 0 : can_be_null(_arg) ? not_null(_arg).to<int64_t>()
# # # # #
# # # ]
5797 [ # # # # ]: 0 : : (discard(_arg), I64x1(1)); // discard since no use
5798 [ # # # # : 0 : auto old_count = _I64x1(r.clone()).insist_not_null();
# # ]
5799 [ # # ]: 0 : r.set_value(
5800 [ # # # # ]: 0 : old_count + new_val_not_null // increment old count by 1 iff new value is present
5801 : : );
5802 : : /* do not update NULL bit since it is already set to `false` */
5803 : 0 : }
5804 : : }
5805 : 0 : break;
5806 : 0 : }
5807 : : }
5808 : : }
5809 [ # # ]: 0 : return { std::move(init_aggs), std::move(update_aggs), std::move(update_avg_aggs) };
5810 : 0 : };
5811 : :
5812 : : /*----- Create function for build child. -----*/
5813 [ # # # # : 0 : FUNCTION(hash_based_group_join_build_child_pipeline, void(void)) // create function for pipeline
# # # # ]
5814 : : {
5815 [ # # # # ]: 0 : auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
5816 : :
5817 [ # # ]: 0 : M.children[0]->execute(
5818 [ # # # # ]: 0 : /* setup= */ setup_t::Make_Without_Parent([&](){
5819 : 0 : ht->setup();
5820 : 0 : ht->set_high_watermark(M.load_factor);
5821 : 0 : dummy.emplace(ht->dummy_entry()); // create dummy slot to ignore NULL values in aggregate computations
5822 : 0 : }),
5823 [ # # # # ]: 0 : /* pipeline= */ [&](){
5824 : 0 : M_insist(bool(dummy));
5825 : 0 : const auto &env = CodeGenContext::Get().env();
5826 : :
5827 : 0 : std::optional<Boolx1> build_key_not_null;
5828 [ # # ]: 0 : for (auto &build_key : build_keys) {
5829 [ # # ]: 0 : auto val = env.get(build_key);
5830 [ # # ]: 0 : if (build_key_not_null)
5831 [ # # # # : 0 : build_key_not_null.emplace(*build_key_not_null and not_null(val));
# # ]
5832 : : else
5833 [ # # # # ]: 0 : build_key_not_null.emplace(not_null(val));
5834 : 0 : }
5835 [ # # ]: 0 : M_insist(bool(build_key_not_null));
5836 [ # # # # ]: 0 : IF (*build_key_not_null) { // TODO: predicated version
5837 : : /*----- Insert key if not yet done. -----*/
5838 : 0 : std::vector<SQL_t> key;
5839 [ # # ]: 0 : for (auto &build_key : build_keys)
5840 [ # # # # ]: 0 : key.emplace_back(env.get(build_key));
5841 [ # # ]: 0 : auto [entry, inserted] = ht->try_emplace(std::move(key));
5842 : :
5843 : : /*----- Compile aggregates. -----*/
5844 [ # # # # ]: 0 : auto t = compile_aggregates(entry, env, M.build.schema(), /* build_phase= */ true);
5845 : 0 : auto &init_aggs = std::get<0>(t);
5846 : 0 : auto &update_aggs = std::get<1>(t);
5847 : 0 : auto &update_avg_aggs = std::get<2>(t);
5848 : :
5849 : : /*----- Add group counters to compiled aggregates. -----*/
5850 [ # # ]: 0 : if (needs_build_counter) {
5851 [ # # # # : 0 : auto r = entry.extract<_I64x1>(C.pool("$build_counter"));
# # ]
5852 [ # # ]: 0 : BLOCK_OPEN(init_aggs) {
5853 [ # # # # : 0 : r.clone() = _I64x1(1); // initialize with 1 (for first value)
# # ]
5854 : : }
5855 [ # # ]: 0 : BLOCK_OPEN(update_aggs) {
5856 [ # # # # : 0 : auto old_count = _I64x1(r.clone()).insist_not_null();
# # ]
5857 [ # # ]: 0 : r.set_value(
5858 [ # # ]: 0 : old_count + int64_t(1) // increment old count by 1
5859 : : );
5860 : : /* do not update NULL bit since it is already set to `false` */
5861 : 0 : }
5862 : 0 : }
5863 [ # # ]: 0 : BLOCK_OPEN(init_aggs) {
5864 [ # # # # : 0 : auto r = entry.extract<_I64x1>(C.pool("$probe_counter"));
# # ]
5865 [ # # # # ]: 0 : r = _I64x1(0); // initialize with neutral element 0
5866 : 0 : }
5867 : :
5868 : : /*----- If group has been inserted, initialize aggregates. Otherwise, update them. -----*/
5869 [ # # ]: 0 : IF (inserted) {
5870 : 0 : init_aggs.attach_to_current();
5871 : 0 : } ELSE {
5872 : 0 : update_aggs.attach_to_current();
5873 : 0 : update_avg_aggs.attach_to_current(); // after others to ensure that running count is incremented before
5874 : 0 : };
5875 : 0 : };
5876 : 0 : },
5877 [ # # ]: 0 : /* teardown= */ teardown_t::Make_Without_Parent([&](){ ht->teardown(); })
5878 : : );
5879 : 0 : }
5880 [ # # ]: 0 : hash_based_group_join_build_child_pipeline(); // call build child function
5881 : :
5882 : : /*----- Create function for probe child. -----*/
5883 [ # # # # : 0 : FUNCTION(hash_based_group_join_probe_child_pipeline, void(void)) // create function for pipeline
# # # # ]
5884 : : {
5885 [ # # # # ]: 0 : auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
5886 : :
5887 [ # # ]: 0 : M.children[1]->execute(
5888 [ # # ]: 0 : /* setup= */ setup_t::Make_Without_Parent([&](){
5889 : 0 : ht->setup();
5890 : 0 : dummy.emplace(ht->dummy_entry()); // create dummy slot to ignore NULL values in aggregate computations
5891 : 0 : }),
5892 [ # # # # ]: 0 : /* pipeline= */ [&](){
5893 : 0 : M_insist(bool(dummy));
5894 : 0 : const auto &env = CodeGenContext::Get().env();
5895 : :
5896 : : /* TODO: may check for NULL on probe keys as well, branching + predicated version */
5897 : : /*----- Probe with probe key. -----*/
5898 : 0 : std::vector<SQL_t> key;
5899 [ # # ]: 0 : for (auto &probe_key : probe_keys)
5900 [ # # # # ]: 0 : key.emplace_back(env.get(probe_key));
5901 [ # # ]: 0 : auto [entry, found] = ht->find(std::move(key));
5902 : :
5903 : : /*----- Compile aggregates. -----*/
5904 [ # # # # ]: 0 : auto t = compile_aggregates(entry, env, M.probe.schema(), /* build_phase= */ false);
5905 : 0 : auto &init_aggs = std::get<0>(t);
5906 : 0 : auto &update_aggs = std::get<1>(t);
5907 : 0 : auto &update_avg_aggs = std::get<2>(t);
5908 : :
5909 : : /*----- Add probe counter to compiled aggregates. -----*/
5910 [ # # ]: 0 : BLOCK_OPEN(update_aggs) {
5911 [ # # # # : 0 : auto r = entry.extract<_I64x1>(C.pool("$probe_counter"));
# # ]
5912 [ # # # # : 0 : auto old_count = _I64x1(r.clone()).insist_not_null();
# # ]
5913 [ # # ]: 0 : r.set_value(
5914 [ # # ]: 0 : old_count + int64_t(1) // increment old count by 1
5915 : : );
5916 : : /* do not update NULL bit since it is already set to `false` */
5917 : 0 : }
5918 : :
5919 : : /*----- If group has been inserted, initialize aggregates. Otherwise, update them. -----*/
5920 [ # # # # ]: 0 : M_insist(init_aggs.empty(), "aggregates must be initialized in build phase");
5921 [ # # ]: 0 : IF (found) {
5922 : 0 : update_aggs.attach_to_current();
5923 : 0 : update_avg_aggs.attach_to_current(); // after others to ensure that running count is incremented before
5924 : 0 : };
5925 : 0 : },
5926 [ # # ]: 0 : /* teardown= */ teardown_t::Make_Without_Parent([&](){ ht->teardown(); })
5927 : : );
5928 : 0 : }
5929 [ # # ]: 0 : hash_based_group_join_probe_child_pipeline(); // call probe child function
5930 : :
5931 [ # # # # ]: 0 : auto &env = CodeGenContext::Get().env();
5932 : :
5933 : : /*----- Process each computed group. -----*/
5934 [ # # # # ]: 0 : setup_t(std::move(setup), [&](){ ht->setup(); })();
5935 [ # # # # ]: 0 : ht->for_each([&, pipeline=std::move(pipeline)](HashTable::const_entry_t entry){
5936 : : /*----- Check whether probe match was found. -----*/
5937 [ # # # # : 0 : I64x1 probe_counter = _I64x1(entry.get<_I64x1>(C.pool("$probe_counter"))).insist_not_null();
# # # # ]
5938 [ # # # # : 0 : IF (probe_counter != int64_t(0)) {
# # ]
5939 : : /*----- Compute key schema to detect duplicated keys. -----*/
5940 : 0 : Schema key_schema;
5941 [ # # ]: 0 : for (std::size_t i = 0; i < num_keys; ++i) {
5942 [ # # ]: 0 : auto &e = M.grouping.schema()[i];
5943 [ # # # # ]: 0 : key_schema.add(e.id, e.type, e.constraints);
5944 : 0 : }
5945 : :
5946 : : /*----- Add computed group tuples to current environment. ----*/
5947 [ # # # # ]: 0 : for (auto &e : M.grouping.schema().deduplicate()) {
5948 : : try {
5949 [ # # ]: 0 : key_schema.find(e.id);
5950 [ # # ]: 0 : } catch (invalid_argument&) {
5951 : : continue; // skip duplicated keys since they must not be used afterwards
5952 [ # # ]: 0 : }
5953 : :
5954 [ # # # # ]: 0 : if (auto it = avg_aggregates.find(e.id);
5955 [ # # ]: 0 : it != avg_aggregates.end() and not it->second.compute_running_avg)
5956 : : { // AVG aggregates which is not yet computed, divide computed sum with computed count
5957 : 0 : auto &avg_info = it->second;
5958 [ # # ]: 0 : auto sum = std::visit(overloaded {
5959 : 0 : [&]<sql_type T>(HashTable::const_reference_t<T> &&r) -> _Doublex1
5960 : : requires (std::same_as<T, _I64x1> or std::same_as<T, _Doublex1>) {
5961 [ # # # # ]: 0 : return T(r).template to<double>();
5962 : 0 : },
5963 : 0 : [](auto&&) -> _Doublex1 { M_unreachable("invalid type"); },
5964 : 0 : [](std::monostate&&) -> _Doublex1 { M_unreachable("invalid reference"); },
5965 [ # # ]: 0 : }, entry.get(avg_info.sum));
5966 [ # # # # : 0 : auto count = _I64x1(entry.get<_I64x1>(avg_info.running_count)).insist_not_null().to<double>();
# # # # ]
5967 [ # # ]: 0 : auto avg = sum / count; // no need to multiply with group counter as the factor would not change the fraction
5968 [ # # ]: 0 : if (avg.can_be_null()) {
5969 [ # # ]: 0 : _Var<Doublex1> var(avg); // introduce variable s.t. uses only load from it
5970 [ # # # # : 0 : env.add(e.id, var);
# # ]
5971 : 0 : } else {
5972 : : /* introduce variable w/o NULL bit s.t. uses only load from it */
5973 [ # # # # ]: 0 : Var<Doublex1> var(avg.insist_not_null());
5974 [ # # # # : 0 : env.add(e.id, _Doublex1(var));
# # # # ]
5975 : 0 : }
5976 : 0 : } else { // part of key or already computed aggregate (without multiplication with group counter)
5977 [ # # ]: 0 : std::visit(overloaded {
5978 : 0 : [&]<typename T>(HashTable::const_reference_t<Expr<T>> &&r) -> void {
5979 : 0 : Expr<T> value = r;
5980 : :
5981 : 0 : auto pred = [&e](const auto &info) -> bool { return info.entry.id == e.id; };
5982 [ # # # # : 0 : if (auto it = std::find_if(aggregates.cbegin(), aggregates.cend(), pred);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
5983 : 0 : it != aggregates.cend())
5984 : : { // aggregate
5985 : : /* For COUNT and SUM, multiply current aggregate value with respective group counter
5986 : : * since only tuples in phase in which argument is bound are counted/summed up. */
5987 [ # # # # : 0 : if (it->args.empty()) {
# # # # #
# # # ]
5988 [ # # # # : 0 : M_insist(it->fnid == m::Function::FN_COUNT,
# # # # #
# # # ]
5989 : : "only COUNT aggregate function may have no argument");
5990 : : I64x1 probe_counter =
5991 [ # # # # : 0 : _I64x1(entry.get<_I64x1>(C.pool("$probe_counter"))).insist_not_null();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
5992 [ # # # # : 0 : PrimitiveExpr<T> count = value.insist_not_null() * probe_counter.to<T>();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
5993 [ # # # # : 0 : Var<PrimitiveExpr<T>> var(count); // introduce variable s.t. uses only load from it
# # # # #
# # # ]
5994 [ # # # # : 0 : env.add(e.id, Expr<T>(var));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
5995 : : return; // next group tuple entry
5996 : 0 : } else {
5997 [ # # # # : 0 : M_insist(it->args.size() == 1, "aggregate functions expect at most one argument");
# # # # #
# # # ]
5998 [ # # # # : 0 : auto &des = as<const Designator>(*it->args[0]);
# # # # #
# # # ]
5999 [ # # # # : 0 : Schema::Identifier arg(des.table_name.text, des.attr_name.text.assert_not_none());
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
6000 [ # # # # : 0 : if (it->fnid == m::Function::FN_COUNT or it->fnid == m::Function::FN_SUM) {
# # # # #
# # # # #
# # # # #
# # # #
# ]
6001 [ # # # # : 0 : if (M.probe.schema().has(arg)) {
# # # # #
# # # # #
# # # # #
# # # #
# ]
6002 : : I64x1 build_counter =
6003 [ # # # # : 0 : _I64x1(entry.get<_I64x1>(C.pool("$build_counter"))).insist_not_null();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
6004 [ # # # # : 0 : auto agg = value * build_counter.to<T>();
# # # # #
# # # # #
# # # # #
# # # #
# ]
6005 [ # # # # : 0 : if (agg.can_be_null()) {
# # # # #
# # # ]
6006 [ # # # # : 0 : Var<Expr<T>> var(agg); // introduce variable s.t. uses only load from it
# # # # #
# # # ]
6007 [ # # # # : 0 : env.add(e.id, var);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
6008 : 0 : } else {
6009 : : /* introduce variable w/o NULL bit s.t. uses only load from it */
6010 [ # # # # : 0 : Var<PrimitiveExpr<T>> var(agg.insist_not_null());
# # # # #
# # # # #
# # # # #
# # # #
# ]
6011 [ # # # # : 0 : env.add(e.id, Expr<T>(var));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
6012 : 0 : }
6013 : 0 : } else {
6014 [ # # # # : 0 : M_insist(M.build.schema().has(arg),
# # # # #
# # # # #
# # # # #
# # # #
# ]
6015 : : "argument ID must occur in either child schema");
6016 : : I64x1 probe_counter =
6017 [ # # # # : 0 : _I64x1(entry.get<_I64x1>(C.pool("$probe_counter"))).insist_not_null();
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
6018 [ # # # # : 0 : auto agg = value * probe_counter.to<T>();
# # # # #
# # # # #
# # # # #
# # # #
# ]
6019 [ # # # # : 0 : if (agg.can_be_null()) {
# # # # #
# # # ]
6020 [ # # # # : 0 : Var<Expr<T>> var(agg); // introduce variable s.t. uses only load from it
# # # # #
# # # ]
6021 [ # # # # : 0 : env.add(e.id, var);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
6022 : 0 : } else {
6023 : : /* introduce variable w/o NULL bit s.t. uses only load from it */
6024 [ # # # # : 0 : Var<PrimitiveExpr<T>> var(agg.insist_not_null());
# # # # #
# # # # #
# # # # #
# # # #
# ]
6025 [ # # # # : 0 : env.add(e.id, Expr<T>(var));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
6026 : 0 : }
6027 : 0 : }
6028 : 0 : return; // next group tuple entry
6029 : : }
6030 [ # # # # : 0 : }
# # # # #
# # # ]
6031 : 0 : }
6032 : :
6033 : : /* fallthrough: part of key or correctly computed aggregate */
6034 [ # # # # : 0 : if (value.can_be_null()) {
# # # # #
# # # ]
6035 [ # # # # : 0 : Var<Expr<T>> var(value); // introduce variable s.t. uses only load from it
# # # # #
# # # ]
6036 [ # # # # : 0 : env.add(e.id, var);
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
6037 : 0 : } else {
6038 : : /* introduce variable w/o NULL bit s.t. uses only load from it */
6039 [ # # # # : 0 : Var<PrimitiveExpr<T>> var(value.insist_not_null());
# # # # #
# # # # #
# # # # #
# # # #
# ]
6040 [ # # # # : 0 : env.add(e.id, Expr<T>(var));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # ]
6041 : 0 : }
6042 [ # # # # : 0 : },
# # # # #
# # # ]
6043 : 0 : [&](HashTable::const_reference_t<_Boolx1> &&r) -> void {
6044 : : #ifndef NDEBUG
6045 : 0 : auto pred = [&e](const auto &info) -> bool { return info.entry.id == e.id; };
6046 : 0 : M_insist(std::find_if(aggregates.cbegin(), aggregates.cend(), pred) == aggregates.cend(),
6047 : : "booleans must not be the result of aggregate functions");
6048 : : #endif
6049 : 0 : _Boolx1 value = r;
6050 [ # # ]: 0 : if (value.can_be_null()) {
6051 [ # # ]: 0 : _Var<Boolx1> var(value); // introduce variable s.t. uses only load from it
6052 [ # # # # : 0 : env.add(e.id, var);
# # ]
6053 : 0 : } else {
6054 : : /* introduce variable w/o NULL bit s.t. uses only load from it */
6055 [ # # # # ]: 0 : Var<Boolx1> var(value.insist_not_null());
6056 [ # # # # : 0 : env.add(e.id, _Boolx1(var));
# # # # ]
6057 : 0 : }
6058 : 0 : },
6059 : 0 : [&](HashTable::const_reference_t<NChar> &&r) -> void {
6060 : : #ifndef NDEBUG
6061 : 0 : auto pred = [&e](const auto &info) -> bool { return info.entry.id == e.id; };
6062 : 0 : M_insist(std::find_if(aggregates.cbegin(), aggregates.cend(), pred) == aggregates.cend(),
6063 : : "strings must not be the result of aggregate functions");
6064 : : #endif
6065 : 0 : NChar value(r);
6066 [ # # # # ]: 0 : Var<Ptr<Charx1>> var(value.val()); // introduce variable s.t. uses only load from it
6067 [ # # # # : 0 : env.add(e.id, NChar(var, value.can_be_null(), value.length(),
# # # # #
# ]
6068 : 0 : value.guarantees_terminating_nul()));
6069 : 0 : },
6070 : 0 : [](std::monostate&&) -> void { M_unreachable("invalid reference"); },
6071 [ # # ]: 0 : }, entry.get(e.id)); // do not extract to be able to access for not-yet-computed AVG aggregates
6072 : : }
6073 : : }
6074 : :
6075 : : /*----- Resume pipeline. -----*/
6076 [ # # ]: 0 : pipeline();
6077 : 0 : };
6078 : 0 : });
6079 [ # # # # ]: 0 : teardown_t(std::move(teardown), [&](){ ht->teardown(); })();
6080 : 0 : }
6081 : :
6082 : :
6083 : : /*======================================================================================================================
6084 : : * Match<T>::print()
6085 : : *====================================================================================================================*/
6086 : :
6087 : : struct print_info
6088 : : {
6089 : : const Operator &op;
6090 : :
6091 : 0 : friend std::ostream & operator<<(std::ostream &out, const print_info &info) {
6092 [ # # ]: 0 : if (info.op.has_info())
6093 : 0 : out << " <" << info.op.info().estimated_cardinality << '>';
6094 : 0 : return out;
6095 : : }
6096 : : };
6097 : :
6098 : 0 : void Match<m::wasm::NoOp>::print(std::ostream &out, unsigned level) const
6099 : : {
6100 : 0 : indent(out, level) << "wasm::NoOp" << print_info(this->noop) << " (cumulative cost " << cost() << ')';
6101 : 0 : this->child->print(out, level + 1);
6102 : 0 : }
6103 : :
6104 : : template<bool SIMDfied>
6105 : 0 : void Match<m::wasm::Callback<SIMDfied>>::print(std::ostream &out, unsigned level) const
6106 : : {
6107 : 0 : indent(out, level) << "wasm::Callback with " << this->result_set_window_size << " tuples result set "
6108 : 0 : << this->callback.schema() << print_info(this->callback)
6109 : 0 : << " (cumulative cost " << cost() << ')';
6110 : 0 : this->child->print(out, level + 1);
6111 : 0 : }
6112 : :
6113 : : template<bool SIMDfied>
6114 : 0 : void Match<m::wasm::Print<SIMDfied>>::print(std::ostream &out, unsigned level) const
6115 : : {
6116 : 0 : indent(out, level) << "wasm::Print with " << this->result_set_window_size << " tuples result set "
6117 : 0 : << this->print_op.schema() << print_info(this->print_op)
6118 : 0 : << " (cumulative cost " << cost() << ')';
6119 : 0 : this->child->print(out, level + 1);
6120 : 0 : }
6121 : :
6122 : : template<bool SIMDfied>
6123 : 0 : void Match<m::wasm::Scan<SIMDfied>>::print(std::ostream &out, unsigned level) const
6124 : : {
6125 : 0 : indent(out, level) << (SIMDfied ? "wasm::SIMDScan(" : "wasm::Scan(") << this->scan.alias() << ") ";
6126 [ # # # # : 0 : if (this->buffer_factory_ and this->scan.schema().drop_constants().deduplicate().num_entries())
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
6127 : 0 : out << "with " << this->buffer_num_tuples_ << " tuples output buffer ";
6128 : 0 : out << this->scan.schema() << print_info(this->scan) << " (cumulative cost " << cost() << ')';
6129 : 0 : }
6130 : :
6131 : : template<idx::IndexMethod IndexMethod>
6132 : 0 : void Match<m::wasm::IndexScan<IndexMethod>>::print(std::ostream &out, unsigned level) const
6133 : : {
6134 : : if (IndexMethod == idx::IndexMethod::Array)
6135 : 0 : indent(out, level) << "wasm::ArrayIndexScan(";
6136 : : else if (IndexMethod == idx::IndexMethod::Rmi)
6137 : 0 : indent(out, level) << "wasm::RecursiveModelIndexScan(";
6138 : : else
6139 : : M_unreachable("unknown index");
6140 : :
6141 [ # # # # ]: 0 : if (options::index_scan_strategy == option_configs::IndexScanStrategy::COMPILATION) {
6142 : 0 : out << "Compilation[";
6143 [ # # # # ]: 0 : if (options::index_scan_compilation_strategy == option_configs::IndexScanCompilationStrategy::CALLBACK)
6144 : 0 : out << "Callback";
6145 [ # # # # ]: 0 : else if (options::index_scan_compilation_strategy == option_configs::IndexScanCompilationStrategy::EXPOSED_MEMORY)
6146 : 0 : out << "ExposedMemory";
6147 : : else
6148 : 0 : M_unreachable("unknown compilation strategy");
6149 [ # # # # ]: 0 : } else if (options::index_scan_strategy == option_configs::IndexScanStrategy::INTERPRETATION) {
6150 : 0 : out << "Interpretation[";
6151 [ # # # # ]: 0 : if (options::index_scan_materialization_strategy == option_configs::IndexScanMaterializationStrategy::INLINE)
6152 : 0 : out << "Inline";
6153 [ # # # # ]: 0 : else if (options::index_scan_materialization_strategy == option_configs::IndexScanMaterializationStrategy::MEMORY)
6154 : 0 : out << "Memory";
6155 : : else
6156 : 0 : M_unreachable("unknown materialization strategy");
6157 [ # # # # ]: 0 : } else if (options::index_scan_strategy == option_configs::IndexScanStrategy::HYBRID) {
6158 : 0 : out << "Hybrid[";
6159 [ # # # # ]: 0 : if (options::index_scan_materialization_strategy == option_configs::IndexScanMaterializationStrategy::INLINE)
6160 : 0 : out << "Inline,";
6161 [ # # # # ]: 0 : else if (options::index_scan_materialization_strategy == option_configs::IndexScanMaterializationStrategy::MEMORY)
6162 : 0 : out << "Memory,";
6163 : : else
6164 : 0 : M_unreachable("unknown materialization strategy");
6165 [ # # # # ]: 0 : if (options::index_scan_compilation_strategy == option_configs::IndexScanCompilationStrategy::CALLBACK)
6166 : 0 : out << "Callback";
6167 [ # # # # ]: 0 : else if (options::index_scan_compilation_strategy == option_configs::IndexScanCompilationStrategy::EXPOSED_MEMORY)
6168 : 0 : out << "ExposedMemory";
6169 : : else
6170 : 0 : M_unreachable("unknown compilation strategy");
6171 : 0 : } else {
6172 : 0 : M_unreachable("unknown strategy");
6173 : : }
6174 : :
6175 : 0 : out << "], " << this->scan.alias() << ", " << this->filter.filter() << ") ";
6176 [ # # # # : 0 : if (this->buffer_factory_ and this->scan.schema().drop_constants().deduplicate().num_entries())
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
6177 : 0 : out << "with " << this->buffer_num_tuples_ << " tuples output buffer ";
6178 : 0 : out << this->scan.schema() << print_info(this->scan) << " (cumulative cost " << cost() << ')';
6179 : 0 : }
6180 : :
6181 : : template<bool Predicated>
6182 : 0 : void Match<m::wasm::Filter<Predicated>>::print(std::ostream &out, unsigned level) const
6183 : : {
6184 : 0 : indent(out, level) << "wasm::" << (Predicated ? "Predicated" : "Branching") << "Filter ";
6185 [ # # # # : 0 : if (this->buffer_factory_ and this->filter.schema().drop_constants().deduplicate().num_entries())
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
6186 : 0 : out << "with " << this->buffer_num_tuples_ << " tuples output buffer ";
6187 : 0 : out << this->filter.schema() << print_info(this->filter) << " (cumulative cost " << cost() << ')';
6188 : 0 : this->child->print(out, level + 1);
6189 : 0 : }
6190 : :
6191 : 0 : void Match<m::wasm::LazyDisjunctiveFilter>::print(std::ostream &out, unsigned level) const
6192 : : {
6193 : 0 : indent(out, level) << "wasm::LazyDisjunctiveFilter ";
6194 [ # # # # : 0 : if (this->buffer_factory_ and this->filter.schema().drop_constants().deduplicate().num_entries())
# # # # #
# # # # #
# # ]
6195 : 0 : out << "with " << this->buffer_num_tuples_ << " tuples output buffer ";
6196 : 0 : const cnf::Clause &clause = this->filter.filter()[0];
6197 [ # # ]: 0 : for (auto it = clause.cbegin(); it != clause.cend(); ++it) {
6198 [ # # ]: 0 : if (it != clause.cbegin()) out << " → ";
6199 : 0 : out << *it;
6200 : 0 : }
6201 : 0 : out << ' ' << this->filter.schema() << print_info(this->filter) << " (cumulative cost " << cost() << ')';
6202 : 0 : this->child->print(out, level + 1);
6203 : 0 : }
6204 : :
6205 : 0 : void Match<m::wasm::Projection>::print(std::ostream &out, unsigned level) const
6206 : : {
6207 : 0 : indent(out, level) << "wasm::Projection ";
6208 [ # # # # : 0 : if (this->buffer_factory_ and this->projection.schema().drop_constants().deduplicate().num_entries())
# # # # #
# # # # #
# # ]
6209 : 0 : out << "with " << this->buffer_num_tuples_ << " tuples output buffer ";
6210 : 0 : out << this->projection.schema() << print_info(this->projection) << " (cumulative cost " << cost() << ')';
6211 [ # # ]: 0 : if (this->child)
6212 : 0 : this->child->get()->print(out, level + 1);
6213 : 0 : }
6214 : :
6215 : 0 : void Match<m::wasm::HashBasedGrouping>::print(std::ostream &out, unsigned level) const
6216 : : {
6217 : 0 : indent(out, level) << "wasm::HashBasedGrouping " << this->grouping.schema() << print_info(this->grouping)
6218 : 0 : << " (cumulative cost " << cost() << ')';
6219 : 0 : this->child->print(out, level + 1);
6220 : 0 : }
6221 : :
6222 : 0 : void Match<m::wasm::OrderedGrouping>::print(std::ostream &out, unsigned level) const
6223 : : {
6224 : 0 : indent(out, level) << "wasm::OrderedGrouping " << this->grouping.schema() << print_info(this->grouping)
6225 : 0 : << " (cumulative cost " << cost() << ')';
6226 : 0 : this->child->print(out, level + 1);
6227 : 0 : }
6228 : :
6229 : 0 : void Match<m::wasm::Aggregation>::print(std::ostream &out, unsigned level) const
6230 : : {
6231 : 0 : indent(out, level) << "wasm::Aggregation " << this->aggregation.schema() << print_info(this->aggregation)
6232 : 0 : << " (cumulative cost " << cost() << ')';
6233 : 0 : this->child->print(out, level + 1);
6234 : 0 : }
6235 : :
6236 : : template<bool CmpPredicated>
6237 : 0 : void Match<m::wasm::Quicksort<CmpPredicated>>::print(std::ostream &out, unsigned level) const
6238 : : {
6239 : 0 : indent(out, level) << "wasm::" << (CmpPredicated ? "Predicated" : "") << "Quicksort " << this->sorting.schema()
6240 : 0 : << print_info(this->sorting) << " (cumulative cost " << cost() << ')';
6241 : 0 : this->child->print(out, level + 1);
6242 : 0 : }
6243 : :
6244 : 0 : void Match<m::wasm::NoOpSorting>::print(std::ostream &out, unsigned level) const
6245 : : {
6246 : 0 : indent(out, level) << "wasm::NoOpSorting" << print_info(this->sorting) << " (cumulative cost " << cost() << ')';
6247 : 0 : this->child->print(out, level + 1);
6248 : 0 : }
6249 : :
6250 : : template<bool Predicated>
6251 : 0 : void Match<m::wasm::NestedLoopsJoin<Predicated>>::print(std::ostream &out, unsigned level) const
6252 : : {
6253 : 0 : indent(out, level) << "wasm::" << (Predicated ? "Predicated" : "") << "NestedLoopsJoin ";
6254 [ # # # # : 0 : if (this->buffer_factory_ and this->join.schema().drop_constants().deduplicate().num_entries())
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
6255 : 0 : out << "with " << this->buffer_num_tuples_ << " tuples output buffer ";
6256 : 0 : out << this->join.schema() << print_info(this->join) << " (cumulative cost " << cost() << ')';
6257 : :
6258 : 0 : ++level;
6259 : 0 : std::size_t i = this->children.size();
6260 [ # # # # ]: 0 : while (i--) {
6261 : 0 : const m::wasm::MatchBase &child = *this->children[i];
6262 : 0 : indent(out, level) << i << ". input";
6263 : 0 : child.print(out, level + 1);
6264 : : }
6265 : 0 : }
6266 : :
6267 : : template<bool Unique, bool Predicated>
6268 : 0 : void Match<m::wasm::SimpleHashJoin<Unique, Predicated>>::print(std::ostream &out, unsigned level) const
6269 : : {
6270 : 0 : indent(out, level) << "wasm::" << (Predicated ? "Predicated" : "") << "SimpleHashJoin";
6271 : 0 : if (Unique) out << " on UNIQUE key ";
6272 [ # # # # : 0 : if (this->buffer_factory_ and this->join.schema().drop_constants().deduplicate().num_entries())
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
6273 : 0 : out << "with " << this->buffer_num_tuples_ << " tuples output buffer ";
6274 : 0 : out << this->join.schema() << print_info(this->join) << " (cumulative cost " << cost() << ')';
6275 : :
6276 : 0 : ++level;
6277 : 0 : const m::wasm::MatchBase &build = *this->children[0];
6278 : 0 : const m::wasm::MatchBase &probe = *this->children[1];
6279 : 0 : indent(out, level) << "probe input";
6280 : 0 : probe.print(out, level + 1);
6281 : 0 : indent(out, level) << "build input";
6282 : 0 : build.print(out, level + 1);
6283 : 0 : }
6284 : :
6285 : : template<bool SortLeft, bool SortRight, bool Predicated, bool CmpPredicated>
6286 : 0 : void Match<m::wasm::SortMergeJoin<SortLeft, SortRight, Predicated, CmpPredicated>>::print(std::ostream &out,
6287 : : unsigned level) const
6288 : : {
6289 : 0 : indent(out, level) << "wasm::" << (Predicated ? "Predicated" : "") << "SortMergeJoin ";
6290 : : switch ((unsigned(SortLeft) << 1) | unsigned(SortRight))
6291 : : {
6292 : 0 : case 0: out << "pre-sorted "; break;
6293 : 0 : case 1: out << "sorting right input " << (CmpPredicated ? "predicated " : ""); break;
6294 : 0 : case 2: out << "sorting left input " << (CmpPredicated ? "predicated " : ""); break;
6295 : 0 : case 3: out << "sorting both inputs " << (CmpPredicated ? "predicated " : ""); break;
6296 : : }
6297 [ # # # # : 0 : const bool needs_buffer_parent = not is<const ScanOperator>(this->parent) or SortLeft;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
6298 [ # # # # : 0 : const bool needs_buffer_child = not is<const ScanOperator>(this->child) or SortRight;
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
6299 [ # # # # : 0 : if (needs_buffer_parent and needs_buffer_child)
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ]
6300 : 0 : out << "and materializing both inputs ";
6301 [ # # # # : 0 : else if (needs_buffer_parent)
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
6302 : 0 : out << "and materializing left input ";
6303 [ # # # # : 0 : else if (needs_buffer_child)
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
6304 : 0 : out << "and materializing right input ";
6305 : 0 : out << this->join.schema() << print_info(this->join) << " (cumulative cost " << cost() << ')';
6306 : :
6307 : 0 : ++level;
6308 : 0 : const m::wasm::MatchBase &left = *this->children[0];
6309 : 0 : const m::wasm::MatchBase &right = *this->children[1];
6310 : 0 : indent(out, level) << "right input";
6311 : 0 : right.print(out, level + 1);
6312 : 0 : indent(out, level) << "left input";
6313 : 0 : left.print(out, level + 1);
6314 : 0 : }
6315 : :
6316 : 0 : void Match<m::wasm::Limit>::print(std::ostream &out, unsigned level) const
6317 : : {
6318 : 0 : indent(out, level) << "wasm::Limit " << this->limit.schema() << print_info(this->limit)
6319 : 0 : << " (cumulative cost " << cost() << ')';
6320 : 0 : this->child->print(out, level + 1);
6321 : 0 : }
6322 : :
6323 : 0 : void Match<m::wasm::HashBasedGroupJoin>::print(std::ostream &out, unsigned level) const
6324 : : {
6325 : 0 : indent(out, level) << "wasm::HashBasedGroupJoin ";
6326 [ # # # # : 0 : if (this->buffer_factory_ and this->grouping.schema().drop_constants().deduplicate().num_entries())
# # # # #
# # # # #
# # ]
6327 : 0 : out << "with " << this->buffer_num_tuples_ << " tuples output buffer ";
6328 : 0 : out << this->grouping.schema() << print_info(this->grouping) << " (cumulative cost " << cost() << ')';
6329 : :
6330 : 0 : ++level;
6331 : 0 : const m::wasm::MatchBase &build = *this->children[0];
6332 : 0 : const m::wasm::MatchBase &probe = *this->children[1];
6333 : 0 : indent(out, level) << "probe input";
6334 : 0 : probe.print(out, level + 1);
6335 : 0 : indent(out, level) << "build input";
6336 : 0 : build.print(out, level + 1);
6337 : 0 : }
6338 : :
6339 : :
6340 : : /*======================================================================================================================
6341 : : * ThePreOrderMatchBaseVisitor, ThePostOrderMatchBaseVisitor
6342 : : *====================================================================================================================*/
6343 : :
6344 : : namespace {
6345 : :
6346 : : template<bool C, bool PreOrder>
6347 : : struct recursive_matchbase_visitor : TheRecursiveMatchBaseVisitorBase<C>
6348 : : {
6349 : : using super = TheRecursiveMatchBaseVisitorBase<C>;
6350 : : template<typename T> using Const = typename super::template Const<T>;
6351 : : using callback_t = std::conditional_t<C, ConstMatchBaseVisitor, MatchBaseVisitor>;
6352 : :
6353 : : private:
6354 : : callback_t &callback_;
6355 : :
6356 : : public:
6357 : 0 : recursive_matchbase_visitor(callback_t &callback) : callback_(callback) { }
6358 : :
6359 : : using super::operator();
6360 : : #define DECLARE(CLASS) \
6361 : : void operator()(Const<CLASS> &M) override { \
6362 : : if constexpr (PreOrder) try { callback_(M); } catch (visit_skip_subtree) { return; } \
6363 : : super::operator()(M); \
6364 : : if constexpr (not PreOrder) callback_(M); \
6365 : : }
6366 [ # # # # : 0 : M_WASM_MATCH_LIST(DECLARE)
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ]
6367 : : #undef DECLARE
6368 : : };
6369 : :
6370 : : }
6371 : :
6372 : : template<bool C>
6373 : 0 : void ThePreOrderMatchBaseVisitor<C>::operator()(Const<MatchBase> &M)
6374 : : {
6375 [ # # ]: 0 : recursive_matchbase_visitor<C, /* PreOrder= */ true>{*this}(M);
6376 : 0 : }
6377 : :
6378 : : template<bool C>
6379 : 0 : void ThePostOrderMatchBaseVisitor<C>::operator()(Const<MatchBase> &M)
6380 : : {
6381 [ # # ]: 0 : recursive_matchbase_visitor<C, /* PreOrder= */ false>{*this}(M);
6382 : 0 : }
6383 : :
6384 : :
6385 : : /*======================================================================================================================
6386 : : * Explicit template instantiations
6387 : : *====================================================================================================================*/
6388 : :
6389 : : #define INSTANTIATE(CLASS) \
6390 : : template struct m::wasm::CLASS; \
6391 : : template struct m::Match<m::wasm::CLASS>;
6392 : : M_WASM_OPERATOR_LIST_TEMPLATED(INSTANTIATE)
6393 : : #undef INSTANTIATE
6394 : :
6395 : : template struct m::wasm::ThePreOrderMatchBaseVisitor<true>;
6396 : : template struct m::wasm::ThePostOrderMatchBaseVisitor<true>;
|