mutable
A Database System for Research and Fast Prototyping
Loading...
Searching...
No Matches
WasmOperator.cpp
Go to the documentation of this file.
2
8#include <mutable/util/fn.hpp>
9#include <numeric>
10
11
12using namespace m;
13using namespace m::ast;
14using namespace m::storage;
15using namespace m::wasm;
16
17
18/*======================================================================================================================
19 * CLI arguments
20 *====================================================================================================================*/
21
22namespace {
23
24__attribute__((constructor(201)))
25static void add_wasm_operator_args()
26{
27 Catalog &C = Catalog::Get();
28
29 /*----- Command-line arguments -----*/
30 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 /* callback= */ [](std::vector<std::string_view> impls){
36 options::scan_implementations = option_configs::ScanImplementation(0UL);
37 for (const auto &elem : impls) {
38 if (strneq(elem.data(), "Scan", elem.size()))
39 options::scan_implementations |= option_configs::ScanImplementation::SCAN;
40 else if (strneq(elem.data(), "IndexScan", elem.size()))
41 options::scan_implementations |= option_configs::ScanImplementation::INDEX_SCAN;
42 else
43 std::cerr << "warning: ignore invalid physical scan implementation " << elem << std::endl;
44 }
45 }
46 );
47 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 /* callback= */ [](std::vector<std::string_view> impls){
54 options::grouping_implementations = option_configs::GroupingImplementation(0UL);
55 for (const auto &elem : impls) {
56 if (strneq(elem.data(), "HashBased", elem.size()))
57 options::grouping_implementations |= option_configs::GroupingImplementation::HASH_BASED;
58 else if (strneq(elem.data(), "Ordered", elem.size()))
59 options::grouping_implementations |= option_configs::GroupingImplementation::ORDERED;
60 else
61 std::cerr << "warning: ignore invalid physical grouping implementation " << elem << std::endl;
62 }
63 }
64 );
65 C.arg_parser().add<std::vector<std::string_view>>(
66 /* group= */ "Wasm",
67 /* short= */ nullptr,
68 /* long= */ "--sorting-implementations",
69 /* description= */ "a comma seperated list of physical sorting implementations to consider (`Quicksort` or "
70 "`NoOp`)",
71 /* callback= */ [](std::vector<std::string_view> impls){
72 options::sorting_implementations = option_configs::SortingImplementation(0UL);
73 for (const auto &elem : impls) {
74 if (strneq(elem.data(), "Quicksort", elem.size()))
75 options::sorting_implementations |= option_configs::SortingImplementation::QUICKSORT;
76 else if (strneq(elem.data(), "NoOp", elem.size()))
77 options::sorting_implementations |= option_configs::SortingImplementation::NOOP;
78 else
79 std::cerr << "warning: ignore invalid physical sorting implementation " << elem << std::endl;
80 }
81 }
82 );
83 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 /* callback= */ [](std::vector<std::string_view> impls){
90 options::join_implementations = option_configs::JoinImplementation(0UL);
91 for (const auto &elem : impls) {
92 if (strneq(elem.data(), "NestedLoops", elem.size()))
93 options::join_implementations |= option_configs::JoinImplementation::NESTED_LOOPS;
94 else if (strneq(elem.data(), "SimpleHash", elem.size()))
95 options::join_implementations |= option_configs::JoinImplementation::SIMPLE_HASH;
96 else if (strneq(elem.data(), "SortMerge", elem.size()))
97 options::join_implementations |= option_configs::JoinImplementation::SORT_MERGE;
98 else
99 std::cerr << "warning: ignore invalid physical join implementation " << elem << std::endl;
100 }
101 }
102 );
103 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 /* callback= */ [](std::vector<std::string_view> impls){
110 options::index_implementations = option_configs::IndexImplementation(0UL);
111 for (const auto &elem : impls) {
112 if (strneq(elem.data(), "Array", elem.size()))
113 options::index_implementations |= option_configs::IndexImplementation::ARRAY;
114 else if (strneq(elem.data(), "Rmi", elem.size()))
115 options::index_implementations |= option_configs::IndexImplementation::RMI;
116 else
117 std::cerr << "warning: ignore invalid index implementation " << elem << std::endl;
118 }
119 }
120 );
121 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 /* callback= */ [](const char *strategy){
127 if (streq(strategy, "Compilation"))
128 options::index_scan_strategy = option_configs::IndexScanStrategy::COMPILATION;
129 else if (streq(strategy, "Interpretation"))
130 options::index_scan_strategy = option_configs::IndexScanStrategy::INTERPRETATION;
131 else if (streq(strategy, "Hybrid"))
132 options::index_scan_strategy = option_configs::IndexScanStrategy::HYBRID;
133 else
134 std::cerr << "warning: ignore invalid index scan strategy " << strategy << std::endl;
135 }
136 );
137 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 /* callback= */ [](const char *strategy){
143 if (streq(strategy, "Callback"))
144 options::index_scan_compilation_strategy = option_configs::IndexScanCompilationStrategy::CALLBACK;
145 else if (streq(strategy, "ExposedMemory"))
146 options::index_scan_compilation_strategy = option_configs::IndexScanCompilationStrategy::EXPOSED_MEMORY;
147 else
148 std::cerr << "warning: ignore invalid index scan strategy " << strategy << std::endl;
149 }
150 );
151 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 /* callback= */ [](const char *strategy){
157 if (streq(strategy, "Inline"))
158 options::index_scan_materialization_strategy = option_configs::IndexScanMaterializationStrategy::INLINE;
159 else if (streq(strategy, "Memory"))
160 options::index_scan_materialization_strategy = option_configs::IndexScanMaterializationStrategy::MEMORY;
161 else
162 std::cerr << "warning: ignore invalid index scan strategy " << strategy << std::endl;
163 }
164 );
165 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 /* callback= */ [](const char *strategy){
171 if (streq(strategy, "Branching"))
172 options::filter_selection_strategy = option_configs::SelectionStrategy::BRANCHING;
173 else if (streq(strategy, "Predicated"))
174 options::filter_selection_strategy = option_configs::SelectionStrategy::PREDICATED;
175 else
176 std::cerr << "warning: ignore invalid filter selection strategy " << strategy << std::endl;
177 }
178 );
179 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 /* callback= */ [](const char *strategy){
185 if (streq(strategy, "Branching"))
186 options::quicksort_cmp_selection_strategy = option_configs::SelectionStrategy::BRANCHING;
187 else if (streq(strategy, "Predicated"))
188 options::quicksort_cmp_selection_strategy = option_configs::SelectionStrategy::PREDICATED;
189 else
190 std::cerr << "warning: ignore invalid quicksort comparison selection strategy " << strategy << std::endl;
191 }
192 );
193 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 /* callback= */ [](const char *strategy){
199 if (streq(strategy, "Branching"))
200 options::nested_loops_join_selection_strategy = option_configs::SelectionStrategy::BRANCHING;
201 else if (streq(strategy, "Predicated"))
202 options::nested_loops_join_selection_strategy = option_configs::SelectionStrategy::PREDICATED;
203 else
204 std::cerr << "warning: ignore invalid nested-loops join selection strategy " << strategy << std::endl;
205 }
206 );
207 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 /* callback= */ [](const char *strategy){
213 if (streq(strategy, "Branching"))
214 options::simple_hash_join_selection_strategy = option_configs::SelectionStrategy::BRANCHING;
215 else if (streq(strategy, "Predicated"))
216 options::simple_hash_join_selection_strategy = option_configs::SelectionStrategy::PREDICATED;
217 else
218 std::cerr << "warning: ignore invalid simple hash join selection strategy " << strategy << std::endl;
219 }
220 );
221 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 /* callback= */ [](const char *strategy){
227 if (streq(strategy, "BuildOnLeft"))
228 options::simple_hash_join_ordering_strategy = option_configs::OrderingStrategy::BUILD_ON_LEFT;
229 else if (streq(strategy, "BuildOnRight"))
230 options::simple_hash_join_ordering_strategy = option_configs::OrderingStrategy::BUILD_ON_RIGHT;
231 else
232 std::cerr << "warning: ignore invalid simple hash join ordering strategy " << strategy << std::endl;
233 }
234 );
235 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 /* callback= */ [](const char *strategy){
241 if (streq(strategy, "Branching"))
242 options::sort_merge_join_selection_strategy = option_configs::SelectionStrategy::BRANCHING;
243 else if (streq(strategy, "Predicated"))
244 options::sort_merge_join_selection_strategy = option_configs::SelectionStrategy::PREDICATED;
245 else
246 std::cerr << "warning: ignore invalid sort merge join selection strategy " << strategy << std::endl;
247 }
248 );
249 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 /* callback= */ [](const char *strategy){
256 if (streq(strategy, "Branching"))
257 options::sort_merge_join_cmp_selection_strategy = option_configs::SelectionStrategy::BRANCHING;
258 else if (streq(strategy, "Predicated"))
259 options::sort_merge_join_cmp_selection_strategy = option_configs::SelectionStrategy::PREDICATED;
260 else
261 std::cerr << "warning: ignore invalid sort merge join comparison selection strategy " << strategy
262 << std::endl;
263 }
264 );
265 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 /* callback= */ [](const char *impl){
271 if (streq(impl, "OpenAddressing"))
272 options::hash_table_implementation = option_configs::HashTableImplementation::OPEN_ADDRESSING;
273 else if (streq(impl, "Chained"))
274 options::hash_table_implementation = option_configs::HashTableImplementation::CHAINED;
275 else
276 std::cerr << "warning: ignore invalid hash table implementation " << impl << std::endl;
277 }
278 );
279 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 /* callback= */ [](const char *strategy){
285 if (streq(strategy, "Linear"))
286 options::hash_table_probing_strategy = option_configs::ProbingStrategy::LINEAR;
287 else if (streq(strategy, "Quadratic"))
288 options::hash_table_probing_strategy = option_configs::ProbingStrategy::QUADRATIC;
289 else
290 std::cerr << "warning: ignore invalid hash table probing strategy " << strategy << std::endl;
291 }
292 );
293 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 /* callback= */ [](const char *strategy){
299 if (streq(strategy, "InPlace"))
300 options::hash_table_storing_strategy = option_configs::StoringStrategy::IN_PLACE;
301 else if (streq(strategy, "OutOfPlace"))
302 options::hash_table_storing_strategy = option_configs::StoringStrategy::OUT_OF_PLACE;
303 else
304 std::cerr << "warning: ignore invalid hash table storing strategy " << strategy << std::endl;
305 }
306 );
307 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 /* callback= */ [](double load_factor){
314 options::load_factor_open_addressing = load_factor;
315 options::load_factor_chained = load_factor;
316 }
317 );
318 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 /* callback= */ [](uint32_t initial_capacity){
324 options::hash_table_initial_capacity = initial_capacity;
325 }
326 );
327 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 /* callback= */ [](bool){ options::hash_based_group_join = false; }
333 );
334 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 /* callback= */ [](const char *layout){
341 if (streq(layout, "Row")) {
342 options::hard_pipeline_breaker_layout = std::make_unique<RowLayoutFactory>();
343 } else {
345 uint64_t block_size;
346 if (streq(layout, "PAX4K")) {
347 size_type = PAXLayoutFactory::NBytes;
348 block_size = 1UL << 12;
349 } else if (streq(layout, "PAX64K")) {
350 size_type = PAXLayoutFactory::NBytes;
351 block_size = 1UL << 16;
352 } else if (streq(layout, "PAX512K")) {
353 size_type = PAXLayoutFactory::NBytes;
354 block_size = 1UL << 19;
355 } else if (streq(layout, "PAX4M")) {
356 size_type = PAXLayoutFactory::NBytes;
357 block_size = 1UL << 22;
358 } else if (streq(layout, "PAX64M")) {
359 size_type = PAXLayoutFactory::NBytes;
360 block_size = 1UL << 26;
361 } else if (streq(layout, "PAX16Tup")) {
362 size_type = PAXLayoutFactory::NTuples;
363 block_size = 16;
364 } else if (streq(layout, "PAX128Tup")) {
365 size_type = PAXLayoutFactory::NTuples;
366 block_size = 128;
367 } else if (streq(layout, "PAX1024Tup")) {
368 size_type = PAXLayoutFactory::NTuples;
369 block_size = 1024;
370 } else {
371 std::cerr << "warning: ignore invalid layout for hard pipeline breakers " << layout << std::endl;
372 }
373 options::hard_pipeline_breaker_layout = std::make_unique<PAXLayoutFactory>(size_type, block_size);
374 }
375 }
376 );
377 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 /* callback= */ [](std::vector<std::string_view> location){
384 options::soft_pipeline_breaker = option_configs::SoftPipelineBreakerStrategy(0UL);
385 for (const auto &elem : location) {
386 if (strneq(elem.data(), "AfterAll", elem.size()))
387 options::soft_pipeline_breaker |= option_configs::SoftPipelineBreakerStrategy::AFTER_ALL;
388 else if (strneq(elem.data(), "AfterScan", elem.size()))
389 options::soft_pipeline_breaker |= option_configs::SoftPipelineBreakerStrategy::AFTER_SCAN;
390 else if (strneq(elem.data(), "AfterFilter", elem.size()))
391 options::soft_pipeline_breaker |= option_configs::SoftPipelineBreakerStrategy::AFTER_FILTER;
392 else if (strneq(elem.data(), "AfterIndexScan", elem.size()))
393 options::soft_pipeline_breaker |= option_configs::SoftPipelineBreakerStrategy::AFTER_INDEX_SCAN;
394 else if (strneq(elem.data(), "AfterProjection", elem.size()))
395 options::soft_pipeline_breaker |= option_configs::SoftPipelineBreakerStrategy::AFTER_PROJECTION;
396 else if (strneq(elem.data(), "AfterNestedLoopsJoin", elem.size()))
397 options::soft_pipeline_breaker |= option_configs::SoftPipelineBreakerStrategy::AFTER_NESTED_LOOPS_JOIN;
398 else if (strneq(elem.data(), "AfterSimpleHashJoin", elem.size()))
399 options::soft_pipeline_breaker |= option_configs::SoftPipelineBreakerStrategy::AFTER_SIMPLE_HASH_JOIN;
400 else
401 std::cerr << "warning: ignore invalid location for soft pipeline breakers " << elem << std::endl;
402 }
403 }
404 );
405 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 /* callback= */ [](const char *layout){
412 if (streq(layout, "Row")) {
413 options::soft_pipeline_breaker_layout = std::make_unique<RowLayoutFactory>();
414 } else {
416 uint64_t block_size;
417 if (streq(layout, "PAX4K")) {
418 size_type = PAXLayoutFactory::NBytes;
419 block_size = 1UL << 12;
420 } else if (streq(layout, "PAX64K")) {
421 size_type = PAXLayoutFactory::NBytes;
422 block_size = 1UL << 16;
423 } else if (streq(layout, "PAX512K")) {
424 size_type = PAXLayoutFactory::NBytes;
425 block_size = 1UL << 19;
426 } else if (streq(layout, "PAX4M")) {
427 size_type = PAXLayoutFactory::NBytes;
428 block_size = 1UL << 22;
429 } else if (streq(layout, "PAX64M")) {
430 size_type = PAXLayoutFactory::NBytes;
431 block_size = 1UL << 26;
432 } else if (streq(layout, "PAX16Tup")) {
433 size_type = PAXLayoutFactory::NTuples;
434 block_size = 16;
435 } else if (streq(layout, "PAX128Tup")) {
436 size_type = PAXLayoutFactory::NTuples;
437 block_size = 128;
438 } else if (streq(layout, "PAX1024Tup")) {
439 size_type = PAXLayoutFactory::NTuples;
440 block_size = 1024;
441 } else {
442 std::cerr << "warning: ignore invalid layout for soft pipeline breakers " << layout << std::endl;
443 }
444 options::soft_pipeline_breaker_layout = std::make_unique<PAXLayoutFactory>(size_type, block_size);
445 }
446 }
447 );
448 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 /* callback= */ [](std::size_t num_tuples){ options::soft_pipeline_breaker_num_tuples = num_tuples; }
454 );
455 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 /* callback= */ [](std::size_t size){ options::index_sequential_scan_batch_size = size; }
463 );
464 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 /* callback= */ [](std::size_t size){ options::result_set_window_size = size; }
470 );
471 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 /* callback= */ [](bool){ options::exploit_unique_build = false; }
477 );
478 C.arg_parser().add<bool>(
479 /* group= */ "Wasm",
480 /* short= */ nullptr,
481 /* long= */ "--no-simd",
482 /* description= */ "disable potential use of SIMDfication",
483 /* callback= */ [](bool){ options::simd = false; }
484 );
485 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 /* callback= */ [](bool){ options::double_pumping = false; }
491 );
492 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 /* callback= */ [](std::size_t lanes){ options::simd_lanes = lanes; }
498 );
499 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 /* callback= */ [&C](std::vector<std::string_view> attrs){
507 for (const auto &elem : attrs) {
508 auto idx = elem.find('.');
509 if (idx == std::string_view::npos)
510 std::cerr << "warning: ignore invalid attribute " << elem << std::endl;
511 Schema::Identifier attr(C.pool(elem.substr(0, idx)), C.pool(elem.substr(idx + 1)));
512 options::sorted_attributes.emplace_back(std::move(attr), true);
513 }
514 }
515 );
516 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 /* callback= */ [&C](std::vector<std::string_view> attrs){
524 for (const auto &elem : attrs) {
525 auto idx = elem.find('.');
526 if (idx == std::string_view::npos)
527 std::cerr << "warning: ignore invalid attribute " << elem << std::endl;
528 Schema::Identifier attr(C.pool(elem.substr(0, idx)), C.pool(elem.substr(idx + 1)));
529 options::sorted_attributes.emplace_back(std::move(attr), false);
530 }
531 }
532 );
533}
534
535}
536
537
538/*======================================================================================================================
539 * register_wasm_operators()
540 *====================================================================================================================*/
541
543{
544 phys_opt.register_operator<NoOp>();
548 phys_opt.register_operator<Print<true>>();
549 if (bool(options::scan_implementations bitand option_configs::ScanImplementation::SCAN)) {
550 phys_opt.register_operator<Scan<false>>();
551 if (options::simd)
552 phys_opt.register_operator<Scan<true>>();
553 }
554 if (bool(options::scan_implementations bitand option_configs::ScanImplementation::INDEX_SCAN)) {
555 if (bool(options::index_implementations bitand option_configs::IndexImplementation::ARRAY))
557 if (bool(options::index_implementations bitand option_configs::IndexImplementation::RMI))
559 }
560 if (bool(options::filter_selection_strategy bitand option_configs::SelectionStrategy::BRANCHING))
562 if (bool(options::filter_selection_strategy bitand option_configs::SelectionStrategy::PREDICATED))
565 phys_opt.register_operator<Projection>();
566 if (bool(options::grouping_implementations bitand option_configs::GroupingImplementation::HASH_BASED))
568 if (bool(options::grouping_implementations bitand option_configs::GroupingImplementation::ORDERED))
570 phys_opt.register_operator<Aggregation>();
571 if (bool(options::sorting_implementations bitand option_configs::SortingImplementation::QUICKSORT)) {
572 if (bool(options::quicksort_cmp_selection_strategy bitand option_configs::SelectionStrategy::BRANCHING))
574 if (bool(options::quicksort_cmp_selection_strategy bitand option_configs::SelectionStrategy::PREDICATED))
576 }
577 if (bool(options::sorting_implementations bitand option_configs::SortingImplementation::NOOP))
578 phys_opt.register_operator<NoOpSorting>();
579 if (bool(options::join_implementations bitand option_configs::JoinImplementation::NESTED_LOOPS)) {
580 if (bool(options::nested_loops_join_selection_strategy bitand option_configs::SelectionStrategy::BRANCHING))
582 if (bool(options::nested_loops_join_selection_strategy bitand option_configs::SelectionStrategy::PREDICATED))
584 }
585 if (bool(options::join_implementations bitand option_configs::JoinImplementation::SIMPLE_HASH)) {
586 if (bool(options::simple_hash_join_selection_strategy bitand option_configs::SelectionStrategy::BRANCHING)) {
588 if (options::exploit_unique_build)
590 }
591 if (bool(options::simple_hash_join_selection_strategy bitand option_configs::SelectionStrategy::PREDICATED)) {
593 if (options::exploit_unique_build)
595 }
596 }
597 if (bool(options::join_implementations bitand option_configs::JoinImplementation::SORT_MERGE)) {
598 if (bool(options::sort_merge_join_selection_strategy bitand option_configs::SelectionStrategy::BRANCHING)) {
599 if (bool(options::sort_merge_join_cmp_selection_strategy bitand option_configs::SelectionStrategy::BRANCHING)) {
604 }
605 if (bool(options::sort_merge_join_cmp_selection_strategy bitand option_configs::SelectionStrategy::PREDICATED)) {
610 }
611 }
612 if (bool(options::sort_merge_join_selection_strategy bitand option_configs::SelectionStrategy::PREDICATED)) {
613 if (bool(options::sort_merge_join_cmp_selection_strategy bitand option_configs::SelectionStrategy::BRANCHING)) {
618 }
619 if (bool(options::sort_merge_join_cmp_selection_strategy bitand option_configs::SelectionStrategy::PREDICATED)) {
624 }
625 }
626 }
627 phys_opt.register_operator<Limit>();
628 if (options::hash_based_group_join)
630}
631
632
633/*======================================================================================================================
634 * Helper structs and functions
635 *====================================================================================================================*/
636
641void write_result_set(const Schema &schema, const DataLayoutFactory &factory, uint32_t window_size,
642 const m::wasm::MatchBase &child)
643{
644 M_insist(schema == schema.drop_constants().deduplicate(), "schema must not contain constants or duplicates");
645 M_insist(CodeGenContext::Get().env().empty(), "all environment entries must be used");
646
647 /*----- Set data layout factory used for the result set. -----*/
649 context.result_set_factory = factory.clone();
650
651 if (schema.num_entries() == 0) { // result set contains only NULL constants
652 if (window_size != 0) { // i.e. result set is materialized only partially
653 M_insist(window_size >= CodeGenContext::Get().num_simd_lanes());
654 M_insist(window_size % CodeGenContext::Get().num_simd_lanes() == 0);
655
656 std::optional<Var<U32x1>> counter;
658 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 FUNCTION(child_pipeline, void(void))
662 {
663 auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
664
665 child.execute(
666 /* setup= */ setup_t::Make_Without_Parent([&](){ counter.emplace(counter_backup); }),
667 /* pipeline= */ [&](){
668 M_insist(bool(counter));
669
670 /*----- Increment tuple ID. -----*/
671 if (auto &env = CodeGenContext::Get().env(); env.predicated()) {
673 "SIMDfication with predication not supported");
674 *counter += env.extract_predicate<_Boolx1>().is_true_and_not_null().to<uint32_t>();
675 } else {
676 *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 IF (*counter == window_size) {
681 CodeGenContext::Get().inc_num_tuples(U32x1(window_size));
682 Module::Get().emit_call<void>("read_result_set", Ptr<void>::Nullptr(), U32x1(window_size));
683 *counter = 0U;
684 };
685 },
686 /* teardown= */ teardown_t::Make_Without_Parent([&](){
687 M_insist(bool(counter));
688 counter_backup = *counter;
689 counter.reset();
690 })
691 );
692 }
693 child_pipeline(); // call child function
694
695 /*----- Update number of result tuples. -----*/
696 CodeGenContext::Get().inc_num_tuples(counter_backup);
697
698 /*----- Extract remaining results. -----*/
699 Module::Get().emit_call<void>("read_result_set", Ptr<void>::Nullptr(), counter_backup.val());
700 } 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 FUNCTION(child_pipeline, void(void))
703 {
704 auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
705
706 std::optional<Var<U32x1>> num_tuples;
707
708 child.execute(
709 /* setup= */ setup_t::Make_Without_Parent([&](){
710 num_tuples.emplace(CodeGenContext::Get().num_tuples());
711 }),
712 /* pipeline= */ [&](){
713 M_insist(bool(num_tuples));
714 if (auto &env = CodeGenContext::Get().env(); env.predicated()) {
716 "SIMDfication with predication not supported");
717 *num_tuples += env.extract_predicate<_Boolx1>().is_true_and_not_null().to<uint32_t>();
718 } else {
719 *num_tuples += uint32_t(CodeGenContext::Get().num_simd_lanes());
720 }
721 },
722 /* teardown= */ teardown_t::Make_Without_Parent([&](){
723 M_insist(bool(num_tuples));
724 CodeGenContext::Get().set_num_tuples(*num_tuples);
725 num_tuples.reset();
726 })
727 );
728 }
729 child_pipeline(); // call child function
730
731 /*----- Extract all results at once. -----*/
732 Module::Get().emit_call<void>("read_result_set", Ptr<void>::Nullptr(), CodeGenContext::Get().num_tuples());
733 }
734 } else { // result set contains contains actual values
735 if (window_size != 0) { // i.e. result set is materialized only partially
737 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 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 FUNCTION(child_pipeline, void(void))
744 {
745 auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
746
747 child.execute(
748 /* setup= */ setup_t::Make_Without_Parent([&](){ result_set.setup(); }),
749 /* pipeline= */ [&](){
750 /*----- Store whether only a single slot is free to not extract result for empty buffer. -----*/
751 const Var<Boolx1> single_slot_free(
752 result_set.size() == window_size - uint32_t(CodeGenContext::Get().num_simd_lanes())
753 );
754
755 /*----- Write the result. -----*/
756 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 IF (single_slot_free and result_set.size() == 0U) {
760 CodeGenContext::Get().inc_num_tuples(U32x1(window_size));
761 Module::Get().emit_call<void>("read_result_set", result_set.base_address(),
762 U32x1(window_size));
763 };
764 },
765 /* teardown= */ teardown_t::Make_Without_Parent([&](){ result_set.teardown(); })
766 );
767 }
768 child_pipeline(); // call child function
769
770 /*----- Update number of result tuples. -----*/
772
773 /*----- Extract remaining results. -----*/
774 Module::Get().emit_call<void>("read_result_set", result_set.base_address(), result_set.size());
775 } else { // i.e. result set is materialized entirely
776 /*----- Create infinite global buffer (without `pipeline`-callback) used as single result set. -----*/
777 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 FUNCTION(child_pipeline, void(void))
781 {
782 auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
783
784 child.execute(
785 /* setup= */ setup_t::Make_Without_Parent([&](){ result_set.setup(); }),
786 /* pipeline= */ [&](){ result_set.consume(); },
787 /* teardown= */ teardown_t::Make_Without_Parent([&](){ result_set.teardown(); })
788 );
789 }
790 child_pipeline(); // call child function
791
792 /*----- Set number of result tuples. -----*/
793 CodeGenContext::Get().inc_num_tuples(result_set.size()); // not inside child function due to predication
794
795 /*----- Extract all results at once. -----*/
796 Module::Get().emit_call<void>("read_result_set", result_set.base_address(), result_set.size());
797 }
798 }
799}
800
803{
806 const std::vector<std::unique_ptr<ast::Expr>> &args;
807};
808
811{
815};
816
823std::pair<std::vector<aggregate_info_t>, std::unordered_map<Schema::Identifier, avg_aggregate_info_t>>
824compute_aggregate_info(const std::vector<std::reference_wrapper<const FnApplicationExpr>> &aggregates,
825 const Schema &schema, std::size_t aggregates_offset = 0)
826{
827 std::vector<aggregate_info_t> aggregates_info;
828 std::unordered_map<Schema::Identifier, avg_aggregate_info_t> avg_aggregates_info;
829
830 for (std::size_t i = aggregates_offset; i < schema.num_entries(); ++i) {
831 auto &e = schema[i];
832
833 auto pred = [&e](const auto &info){ return info.entry.id == e.id; };
834 if (auto it = std::find_if(aggregates_info.cbegin(), aggregates_info.cend(), pred); it != aggregates_info.cend())
835 continue; // duplicated aggregate
836
837 auto &fn_expr = aggregates[i - aggregates_offset].get();
838 auto &fn = fn_expr.get_function();
839 M_insist(fn.kind == m::Function::FN_Aggregate, "not an aggregation function");
840
841 if (fn.fnid == m::Function::FN_AVG) {
842 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 auto pred = [&fn_expr](const auto &_fn_expr){
846 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 return _fn_expr.get().get_function().fnid == m::Function::FN_COUNT and
849 not _fn_expr.get().args.empty() and *_fn_expr.get().args[0] == *fn_expr.args[0];
850 };
851 std::optional<Schema::Identifier> running_count;
852 if (auto it = std::find_if(aggregates.cbegin(), aggregates.cend(), pred);
853 it != aggregates.cend())
854 { // reuse found running count
855 const auto idx_agg = std::distance(aggregates.cbegin(), it);
856 running_count = schema[aggregates_offset + idx_agg].id;
857 } else { // insert additional running count
858 std::ostringstream oss;
859 oss << "$running_count_" << fn_expr;
860 running_count = Schema::Identifier(Catalog::Get().pool(oss.str().c_str()));
861 aggregates_info.emplace_back(aggregate_info_t{
862 .entry = { *running_count, Type::Get_Integer(Type::TY_Scalar, 8), Schema::entry_type::NOT_NULLABLE },
863 .fnid = m::Function::FN_COUNT,
864 .args = fn_expr.args
865 });
866 }
867
868 /*----- Decide how to compute the average aggregate and insert sum aggregate accordingly. -----*/
869 std::optional<Schema::Identifier> sum;
870 bool compute_running_avg;
871 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 compute_running_avg = false;
875 auto pred = [&fn_expr](const auto &_fn_expr){
876 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 return _fn_expr.get().get_function().fnid == m::Function::FN_SUM and
880 *_fn_expr.get().args[0] == *fn_expr.args[0];
881 };
882 if (auto it = std::find_if(aggregates.cbegin(), aggregates.cend(), pred);
883 it != aggregates.cend())
884 { // reuse found SUM aggregate
885 const auto idx_agg = std::distance(aggregates.cbegin(), it);
886 sum = schema[aggregates_offset + idx_agg].id;
887 } else { // insert additional SUM aggregate
888 std::ostringstream oss;
889 oss << "$sum_" << fn_expr;
890 sum = Schema::Identifier(Catalog::Get().pool(oss.str().c_str()));
891 const Type *type;
892 switch (as<const Numeric>(*fn_expr.args[0]->type()).kind) {
893 case Numeric::N_Int:
894 case Numeric::N_Decimal:
895 type = Type::Get_Integer(Type::TY_Scalar, 8);
896 break;
897 case Numeric::N_Float:
898 type = Type::Get_Double(Type::TY_Scalar);
899 }
900 aggregates_info.emplace_back(aggregate_info_t{
901 .entry = { *sum, type, e.constraints },
902 .fnid = m::Function::FN_SUM,
903 .args = fn_expr.args
904 });
905 }
906 } else {
907 /* Compute average by computing a running average for each inserted value in a `_Doublex1` field (since
908 * the sum may overflow). */
909 compute_running_avg = true;
910 M_insist(e.type->is_double());
911 aggregates_info.emplace_back(aggregate_info_t{
912 .entry = e,
913 .fnid = m::Function::FN_AVG,
914 .args = fn_expr.args
915 });
916 }
917
918 /*----- Add info for this AVG aggregate. -----*/
919 avg_aggregates_info.try_emplace(e.id, avg_aggregate_info_t{
920 .running_count = std::move(*running_count),
921 .sum = std::move(*sum),
922 .compute_running_avg = compute_running_avg
923 });
924 } else {
925 aggregates_info.emplace_back(aggregate_info_t{
926 .entry = e,
927 .fnid = fn.fnid,
928 .args = fn_expr.args
929 });
930 }
931 }
932
933 return { std::move(aggregates_info), std::move(avg_aggregates_info) };
934}
935
939std::pair<std::vector<Schema::Identifier>, std::vector<Schema::Identifier>>
940decompose_equi_predicate(const cnf::CNF &cnf, const Schema &schema_left)
941{
942 std::vector<Schema::Identifier> ids_left, ids_right;
943 for (auto &clause : cnf) {
944 M_insist(clause.size() == 1, "invalid equi-predicate");
945 auto &literal = clause[0];
946 auto &binary = as<const BinaryExpr>(literal.expr());
947 M_insist((not literal.negative() and binary.tok == TK_EQUAL) or
948 (literal.negative() and binary.tok == TK_BANG_EQUAL), "invalid equi-predicate");
949 M_insist(is<const Designator>(binary.lhs), "invalid equi-predicate");
950 M_insist(is<const Designator>(binary.rhs), "invalid equi-predicate");
951 Schema::Identifier id_first(*binary.lhs), id_second(*binary.rhs);
952 const auto &[id_left, id_right] = schema_left.has(id_first) ? std::make_pair(id_first, id_second)
953 : std::make_pair(id_second, id_first);
954 ids_left.push_back(std::move(id_left));
955 ids_right.push_back(std::move(id_right));
956 }
957 M_insist(ids_left.size() == ids_right.size(), "number of found IDs differ");
958 M_insist(not ids_left.empty(), "must find at least one ID");
959 return { std::move(ids_left), std::move(ids_right) };
960}
961
963U32x1 get_num_rows(const ThreadSafePooledString &table_name) {
964 static std::ostringstream oss;
965 oss.str("");
966 oss << table_name << "_num_rows";
967 return Module::Get().get_global<uint32_t>(oss.str().c_str());
968}
969
972 static std::ostringstream oss;
973 oss.str("");
974 oss << table_name << "_mem";
975 return Module::Get().get_global<void*>(oss.str().c_str());
976}
977
980 static std::ostringstream oss;
981 oss.str("");
982 oss << "index_" << table_name << '_' << attr_name << "_array" << "_num_entries";
983 return Module::Get().get_global<uint32_t>(oss.str().c_str());
984}
985
988 static std::ostringstream oss;
989 oss.str("");
990 oss << "index_" << table_name << '_' << attr_name << "_array" << "_mem";
991 return Module::Get().get_global<void*>(oss.str().c_str());
992}
993
996uint32_t compute_initial_ht_capacity(const Operator &op, double load_factor) {
997 uint64_t initial_capacity;
998 if (options::hash_table_initial_capacity) {
999 initial_capacity = *options::hash_table_initial_capacity;
1000 } else {
1001 if (op.has_info())
1002 initial_capacity = static_cast<uint64_t>(std::ceil(op.info().estimated_cardinality / load_factor));
1003 else if (auto scan = cast<const ScanOperator>(&op))
1004 initial_capacity = static_cast<uint64_t>(std::ceil(scan->store().num_rows() / load_factor));
1005 else
1006 initial_capacity = 1024; // fallback
1007 }
1008 return std::in_range<uint32_t>(initial_capacity) ? initial_capacity : std::numeric_limits<uint32_t>::max();
1009}
1010
1013{
1015 std::optional<std::reference_wrapper<const ast::Expr>> lo, hi;
1017};
1018
1021 if (is<const Constant>(expr))
1022 return true;
1023 if (auto u = cast<const UnaryExpr>(&expr)) {
1024 /* `UnaryExpr` must be followed by `Constant`. */
1025 if ((u->op().type == TK_MINUS or u->op().type == TK_PLUS) and is<const Constant>(*u->expr))
1026 return true;
1027 }
1028 return false;
1029}
1030
1033std::pair<const Constant&, bool> get_valid_bound(const ast::Expr &expr) {
1034 M_insist(is_valid_bound(expr), "bound must be valid");
1035 if (auto c = cast<const Constant>(&expr))
1036 return { *c, false };
1037 auto &u = as<const UnaryExpr>(expr);
1038 return { as<const Constant>(*u.expr), u.op().type == TK_MINUS };
1039}
1040
1045{
1046 index_scan_bounds_t bounds;
1047
1048 M_insist(not cnf.empty(), "filter condition must not be empty");
1049 auto designators = cnf.get_required();
1050 M_insist(designators.num_entries() == 1, "filter condition must contain exactly one designator");
1051 bounds.attribute = designators[0];
1052
1053 for (auto &clause : cnf) {
1054 M_insist(clause.size() == 1, "invalid predicate");
1055 auto &literal = clause[0];
1056 auto &binary = as<const BinaryExpr>(literal.expr());
1057 M_insist((is<const Designator>(binary.lhs) and is_valid_bound(*binary.rhs)) or
1058 (is<const Designator>(binary.rhs) and is_valid_bound(*binary.lhs)), "invalid predicate");
1059 bool has_attribute_left = is<const Designator>(binary.lhs);
1060 auto &bound = has_attribute_left ? *binary.rhs : *binary.lhs;
1061
1062 switch(binary.tok.type) {
1063 default:
1064 M_unreachable("unsupported token type");
1065 case TK_EQUAL:
1066 M_insist(not bool(bounds.lo) and not bool(bounds.hi), "bound already set");
1067 bounds.lo = bounds.hi = std::cref(bound);
1068 bounds.is_inclusive_lo = bounds.is_inclusive_hi = true;
1069 break;
1070 case TK_GREATER:
1071 if (has_attribute_left) {
1072 M_insist(not bool(bounds.lo), "lo bound already set");
1073 bounds.lo = std::cref(bound);
1074 bounds.is_inclusive_lo = false;
1075 } else {
1076 M_insist(not bool(bounds.hi), "hi bound already set");
1077 bounds.hi = std::cref(bound);
1078 bounds.is_inclusive_hi = false;
1079 }
1080 break;
1081 case TK_GREATER_EQUAL:
1082 if (has_attribute_left) {
1083 M_insist(not bool(bounds.lo), "lo bound already set");
1084 bounds.lo = std::cref(bound);
1085 bounds.is_inclusive_lo = true;
1086 } else {
1087 M_insist(not bool(bounds.hi), "hi bound already set");
1088 bounds.hi = std::cref(bound);
1089 bounds.is_inclusive_hi = true;
1090 }
1091 break;
1092 case TK_LESS:
1093 if (has_attribute_left) {
1094 M_insist(not bool(bounds.hi), "hi bound already set");
1095 bounds.hi = std::cref(bound);
1096 bounds.is_inclusive_hi = false;
1097 } else {
1098 M_insist(not bool(bounds.lo), "lo bound already set");
1099 bounds.lo = std::cref(bound);
1100 bounds.is_inclusive_lo = false;
1101 }
1102 break;
1103 case TK_LESS_EQUAL:
1104 if (has_attribute_left) {
1105 M_insist(not bool(bounds.hi), "hi bound already set");
1106 bounds.hi = std::cref(bound);
1107 bounds.is_inclusive_hi = true;
1108 } else {
1109 M_insist(not bool(bounds.lo), "lo bound already set");
1110 bounds.lo = std::cref(bound);
1111 bounds.is_inclusive_lo = true;
1112 }
1113 break;
1114 }
1115 }
1116 M_insist(bool(bounds.lo) or bool(bounds.hi), "either bound must be set");
1117 return bounds;
1118}
1119
1120
1121/*======================================================================================================================
1122 * NoOp
1123 *====================================================================================================================*/
1124
1126{
1127 std::optional<Var<U32x1>> num_tuples;
1128
1129 M.child->execute(
1130 /* setup= */ setup_t::Make_Without_Parent([&](){ num_tuples.emplace(CodeGenContext::Get().num_tuples()); }),
1131 /* pipeline= */ [&](){
1132 M_insist(bool(num_tuples));
1133 if (auto &env = CodeGenContext::Get().env(); env.predicated()) {
1134 switch (CodeGenContext::Get().num_simd_lanes()) {
1135 default: M_unreachable("invalid number of simd lanes");
1136 case 1: {
1137 *num_tuples += env.extract_predicate<_Boolx1>().is_true_and_not_null().to<uint32_t>();
1138 break;
1139 }
1140 case 16: {
1141 auto pred = env.extract_predicate<_Boolx16>().is_true_and_not_null();
1142 *num_tuples += pred.bitmask().popcnt();
1143 break;
1144 }
1145 }
1146 } else {
1147 *num_tuples += uint32_t(CodeGenContext::Get().num_simd_lanes());
1148 }
1149 },
1150 /* teardown= */ teardown_t::Make_Without_Parent([&](){
1151 M_insist(bool(num_tuples));
1152 CodeGenContext::Get().set_num_tuples(*num_tuples);
1153 num_tuples.reset();
1154 })
1155 );
1156}
1157
1158
1159/*======================================================================================================================
1160 * Callback
1161 *====================================================================================================================*/
1162
1163template<bool SIMDfied>
1164ConditionSet Callback<SIMDfied>::pre_condition(std::size_t child_idx, const std::tuple<const CallbackOperator*>&)
1165{
1166 M_insist(child_idx == 0);
1167
1168 ConditionSet pre_cond;
1169
1170 if constexpr (SIMDfied) {
1171 /*----- SIMDfied callback supports SIMD but not predication. -----*/
1172 pre_cond.add_condition(Predicated(false));
1173 } else {
1174 /*----- Non-SIMDfied callback does not support SIMD. -----*/
1175 pre_cond.add_condition(NoSIMD());
1176 }
1177
1178 return pre_cond;
1179}
1180
1181template<bool SIMDfied>
1183{
1184 M_insist(bool(M.result_set_factory), "`wasm::Callback` must have a factory for the result set");
1185
1186 auto result_set_schema = M.callback.schema().drop_constants().deduplicate();
1187 write_result_set(result_set_schema, *M.result_set_factory, M.result_set_window_size, *M.child);
1188}
1189
1190
1191/*======================================================================================================================
1192 * Print
1193 *====================================================================================================================*/
1194
1195template<bool SIMDfied>
1196ConditionSet Print<SIMDfied>::pre_condition(std::size_t child_idx, const std::tuple<const PrintOperator*>&)
1197{
1198 M_insist(child_idx == 0);
1199
1200 ConditionSet pre_cond;
1201
1202 if constexpr (SIMDfied) {
1203 /*----- SIMDfied print supports SIMD but not predication. -----*/
1204 pre_cond.add_condition(Predicated(false));
1205 } else {
1206 /*----- Non-SIMDfied print does not support SIMD. -----*/
1207 pre_cond.add_condition(NoSIMD());
1208 }
1209
1210 return pre_cond;
1211}
1212
1213template<bool SIMDfied>
1215{
1216 M_insist(bool(M.result_set_factory), "`wasm::Print` must have a factory for the result set");
1217
1218 auto result_set_schema = M.print_op.schema().drop_constants().deduplicate();
1219 write_result_set(result_set_schema, *M.result_set_factory, M.result_set_window_size, *M.child);
1220}
1221
1222
1223/*======================================================================================================================
1224 * Scan
1225 *====================================================================================================================*/
1226
1227template<bool SIMDfied>
1229 const std::tuple<const ScanOperator*> &partial_inner_nodes)
1230{
1231 M_insist(child_idx == 0);
1232
1233 ConditionSet pre_cond;
1234
1235 if constexpr (SIMDfied) {
1236 auto &scan = *std::get<0>(partial_inner_nodes);
1237 auto &table = scan.store().table();
1238
1239 /*----- SIMDfied scan needs the data layout to support SIMD. -----*/
1240 if (not supports_simd(table.layout(), table.schema(scan.alias()), scan.schema()))
1242
1243 /*----- SIMDfied scan needs the number of rows to load be a whole multiple of the number of SIMD lanes used. -*/
1244 if (scan.store().num_rows() % get_num_simd_lanes(table.layout(), table.schema(scan.alias()), scan.schema()) != 0)
1246 }
1247
1248 return pre_cond;
1249}
1250
1251template<bool SIMDfied>
1253{
1254 ConditionSet post_cond;
1255
1256 /*----- Scan does not introduce predication. -----*/
1257 post_cond.add_condition(Predicated(false));
1258
1259 if constexpr (SIMDfied) {
1260 /*----- SIMDfied scan introduces SIMD vectors with respective number of lanes. -----*/
1261 auto &table = M.scan.store().table();
1262 const auto num_simd_lanes = get_num_simd_lanes(table.layout(), table.schema(M.scan.alias()), M.scan.schema());
1263 post_cond.add_condition(SIMD(num_simd_lanes));
1264 } else {
1265 /*----- Non-SIMDfied scan does not introduce SIMD. -----*/
1266 post_cond.add_condition(NoSIMD());
1267 }
1268
1269 /*----- Check if any attribute of scanned table is assumed to be sorted. -----*/
1270 Sortedness::order_t orders;
1271 for (auto &e : M.scan.schema()) {
1272 auto pred = [&e](const auto &p){ return e.id == p.first; };
1273 if (auto it = std::find_if(options::sorted_attributes.cbegin(), options::sorted_attributes.cend(), pred);
1274 it != options::sorted_attributes.cend())
1275 {
1276 orders.add(e.id, it->second ? Sortedness::O_ASC : Sortedness::O_DESC);
1277 }
1278 }
1279 if (not orders.empty())
1280 post_cond.add_condition(Sortedness(std::move(orders)));
1281
1282 return post_cond;
1283}
1284
1285template<bool SIMDfied>
1286void Scan<SIMDfied>::execute(const Match<Scan> &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
1287{
1288 auto &schema = M.scan.schema();
1289 auto &table = M.scan.store().table();
1290
1291 M_insist(schema == schema.drop_constants().deduplicate(), "schema of `ScanOperator` must not contain NULL or duplicates");
1292 M_insist(not table.layout().is_finite(), "layout for `wasm::Scan` must be infinite");
1293
1294 Var<U32x1> tuple_id; // default initialized to 0
1295
1296 /*----- Compute possible number of SIMD lanes and decide which to use with regard to other operators preferences. */
1297 const auto layout_schema = table.schema(M.scan.alias());
1299 const auto num_simd_lanes_preferred =
1300 CodeGenContext::Get().num_simd_lanes_preferred(); // get other operators preferences
1301 const std::size_t num_simd_lanes =
1302 SIMDfied ? (options::double_pumping ? 2 : 1)
1303 * std::max(num_simd_lanes_preferred, get_num_simd_lanes(table.layout(), layout_schema, schema))
1304 : 1;
1306
1307 /*----- Import the number of rows of `table`. -----*/
1308 U32x1 num_rows = get_num_rows(table.name());
1309
1310 /*----- If no attributes must be loaded, generate a loop just executing the pipeline `num_rows`-times. -----*/
1311 if (schema.num_entries() == 0) {
1312 setup();
1313 WHILE (tuple_id < num_rows) {
1314 tuple_id += uint32_t(num_simd_lanes);
1315 pipeline();
1316 }
1317 teardown();
1318 return;
1319 }
1320
1321 /*----- Import the base address of the mapped memory. -----*/
1322 Ptr<void> base_address = get_base_address(table.name());
1323
1324 /*----- Emit setup code *before* compiling data layout to not overwrite its temporary boolean variables. -----*/
1325 setup();
1326
1327 /*----- Compile data layout to generate sequential load from table. -----*/
1328 static Schema empty_schema;
1329 auto [inits, loads, jumps] = compile_load_sequential(schema, empty_schema, base_address, table.layout(),
1330 num_simd_lanes, layout_schema, tuple_id);
1331
1332 /*----- Generate the loop for the actual scan, with the pipeline emitted into the loop body. -----*/
1333 inits.attach_to_current();
1334 WHILE (tuple_id < num_rows) {
1335 loads.attach_to_current();
1336 pipeline();
1337 jumps.attach_to_current();
1338 }
1339
1340 /*----- Emit teardown code. -----*/
1341 teardown();
1342}
1343
1344
1345/*======================================================================================================================
1346 * Index Scan
1347 *====================================================================================================================*/
1348
1349template<idx::IndexMethod IndexMethod>
1351 const std::tuple<const FilterOperator*,
1352 const ScanOperator*> &partial_inner_nodes)
1353{
1354 M_insist(child_idx == 0);
1355
1356 /*----- Check if index on one of the attributes in filter condition exists. -----*/
1357 auto &filter = *std::get<0>(partial_inner_nodes);
1358 auto &cnf = filter.filter();
1359
1360 M_insist(not cnf.empty(), "Filter condition must not be empty");
1361
1362 auto &scan = *std::get<1>(partial_inner_nodes);
1363 auto &table = scan.store().table();
1364
1365 Catalog &C = Catalog::Get();
1366 auto &DB = C.get_database_in_use();
1367
1368 /* Extract attributes from filter condition. */
1369 std::vector<Schema::Identifier> ids;
1370 for (auto &entry : cnf.get_required()) {
1371 Schema::Identifier &id = entry.id;
1372 M_insist(table.name() == id.prefix, "Table name should match designator table name");
1373
1374 /* Keep attributes for which an index exists. */
1375 if (DB.has_index(table.name(), id.name, IndexMethod))
1376 ids.push_back(id);
1377 }
1378 if (ids.empty()) // no usable index found
1380
1381 /*----- Check if filter condition is supported. -----*/
1382 /* We currently only support filter conditions of the following form.
1383 * 1. Point: condition with a single equality predicate, e.g.
1384 * x = 42.
1385 * 2. One-sided range: condition with a single greater/greater-or-equal/less/less-or-equal predicate, e.g.
1386 * x > 42.
1387 * 3. Two-sided range: condition with a greater/greater-or-equal predicate and a less/less-or-equal predicate, e.g.
1388 * x > 42 AND x <= 89.
1389 * Attributes may appear on either side. The other side is required to be a `Constant`. */
1390 if (ids.size() > 1) // conditions with more than one attribute currently not supported
1392
1393 bool has_lo_bound = false;
1394 bool has_hi_bound = false;
1395 for (auto &clause : cnf) {
1396 if (clause.size() != 1) // disjunctions not supported
1398
1399 auto &predicate = clause[0];
1400 if (predicate.negative()) // negated predicates not supported
1402
1403 auto expr = cast<const BinaryExpr>(&predicate.expr());
1404 if (not expr) // not a binary expression
1406
1407 bool has_attribute_left = is<const Designator>(expr->lhs);
1408 auto &attribute = has_attribute_left ? *expr->lhs : *expr->rhs;
1409 auto &constant = has_attribute_left ? *expr->rhs : *expr->lhs;
1410 if (not is<const Designator>(attribute)) // attribute must be on lhs
1412 if (not is_valid_bound(constant))
1414
1415 switch(expr->tok.type) {
1416 default:
1418 case TK_EQUAL:
1419 if (not has_lo_bound and not has_hi_bound) { // lo and hi bound not yet set
1420 has_lo_bound = has_hi_bound = true;
1421 } else {
1423 }
1424 break;
1425 case TK_GREATER:
1426 case TK_GREATER_EQUAL:
1427 if (has_attribute_left and not has_lo_bound) { // attribute on lhs, lo bound not yet set
1428 has_lo_bound = true;
1429 } else if (not has_attribute_left and not has_hi_bound) { // attribute on rhs, hi bound not yet set
1430 has_hi_bound = true;
1431 } else {
1433 }
1434 break;
1435 case TK_LESS:
1436 case TK_LESS_EQUAL:
1437 if (has_attribute_left and not has_hi_bound) { // attribute on lhs, hi bound not yet set
1438 has_hi_bound = true;
1439 } else if (not has_attribute_left and not has_lo_bound) { // attribute on rhs, lo bound not yet set
1440 has_lo_bound = true;
1441 } else {
1443 }
1444 break;
1445 }
1446 }
1447 return ConditionSet();
1448}
1449
1450template<idx::IndexMethod IndexMethod>
1452{
1453 ConditionSet post_cond;
1454
1455 /*----- Index scan does not introduce predication. -----*/
1456 post_cond.add_condition(Predicated(false));
1457
1458 /*----- Non-SIMDfied index scan does not introduce SIMD. -----*/
1459 post_cond.add_condition(NoSIMD());
1460
1461 /*----- Index scan introduces sortedness on indexed attribute. -----*/
1462 /* Extract identifier from cnf. */
1463 auto &cnf = M.filter.filter();
1464 Schema designators = cnf.get_required();
1465 M_insist(designators.num_entries() == 1, "filter condition must contain exactly one designator");
1466 Schema::Identifier &id = designators[0].id;
1467
1468 /* Add sortedness post condition. */
1469 Sortedness::order_t orders;
1470 orders.add(id, Sortedness::O_ASC);
1471 post_cond.add_condition(Sortedness(std::move(orders)));
1472
1473 return post_cond;
1474}
1475
1476template<idx::IndexMethod IndexMethod>
1478{
1479 /* TODO: add meaningful cost function. */
1480 return 0.0;
1481}
1482
1483template<idx::IndexMethod IndexMethod, typename Index, sql_type SqlT>
1484void index_scan_codegen_compilation(const Index &index, const index_scan_bounds_t &bounds,
1485 const Match<IndexScan<IndexMethod>> &M,
1486 setup_t setup, pipeline_t pipeline, teardown_t teardown) {
1487 using key_type = Index::key_type;
1488 using value_type = Index::value_type;
1489 constexpr uint32_t entry_size = sizeof(typename Index::entry_type);
1490 using sql_type = SqlT;
1491
1492 auto table_name = M.scan.store().table().name();
1493 auto attr_name = bounds.attribute.id.name;
1494
1496 /*----- Resolve callback function names. -----*/
1497 const char *scan_fn, *lower_bound_fn, *upper_bound_fn;
1498#define SET_CALLBACK_FNS(INDEX, KEY) \
1499 scan_fn = M_STR(idx_scan_##INDEX##_##KEY); \
1500 lower_bound_fn = M_STR(idx_lower_bound_##INDEX##_##KEY); \
1501 upper_bound_fn = M_STR(idx_upper_bound_##INDEX##_##KEY)
1502
1503#define RESOLVE_KEYTYPE(INDEX) \
1504 if constexpr(std::same_as<SqlT, _Boolx1>) { \
1505 SET_CALLBACK_FNS(INDEX, b); \
1506 } else if constexpr(std::same_as<sql_type, _I8x1>) { \
1507 SET_CALLBACK_FNS(INDEX, i1); \
1508 } else if constexpr(std::same_as<sql_type, _I16x1>) { \
1509 SET_CALLBACK_FNS(INDEX, i2); \
1510 } else if constexpr(std::same_as<sql_type, _I32x1>) { \
1511 SET_CALLBACK_FNS(INDEX, i4); \
1512 } else if constexpr(std::same_as<sql_type, _I64x1>) { \
1513 SET_CALLBACK_FNS(INDEX, i8); \
1514 } else if constexpr(std::same_as<sql_type, _Floatx1>) { \
1515 SET_CALLBACK_FNS(INDEX, f); \
1516 } else if constexpr(std::same_as<sql_type, _Doublex1>) { \
1517 SET_CALLBACK_FNS(INDEX, d); \
1518 } else if constexpr(std::same_as<sql_type, NChar>) { \
1519 SET_CALLBACK_FNS(IDNEX, p); \
1520 } else { \
1521 M_unreachable("incompatible SQL type"); \
1522 }
1523 if constexpr(is_specialization<Index, idx::ArrayIndex>) {
1524 RESOLVE_KEYTYPE(array)
1525 } else if constexpr(is_specialization<Index, idx::RecursiveModelIndex>) {
1526 RESOLVE_KEYTYPE(rmi)
1527 } else {
1528 M_unreachable("unknown index type");
1529 }
1530#undef RESOLVE_KEYTYPE
1531#undef SET_CALLBACK_FNS
1532
1533 /*----- Add index to context. -----*/
1535 U64x1 index_id(context.add_index(index));
1536
1537 /*----- Emit host calls to query the index for lo and hi bounds. -----*/
1538 auto compile_bound_lookup = [&](const ast::Expr &bound, bool is_lower_bound) {
1539 auto [constant, is_negative] = get_valid_bound(bound);
1540 auto c = Interpreter::eval(constant);
1541 key_type _key;
1542 if constexpr(m::boolean<key_type>) {
1543 _key = bool(c);
1544 M_insist(not is_negative, "boolean cannot be negative");
1545 } else if constexpr(m::integral<key_type>) {
1546 auto i64 = int64_t(c);
1547 M_insist(std::in_range<key_type>(i64), "integral constant must be in range");
1548 _key = key_type(i64);
1549 _key = is_negative ? -_key : _key;
1550 } else if constexpr(std::same_as<float, key_type>) {
1551 auto d = double(c);
1552 _key = key_type(d);
1553 M_insist(_key == d, "downcasting should not impact precision");
1554 _key = is_negative ? -_key : _key;
1555 } else if constexpr(std::same_as<double, key_type>) {
1556 _key = double(c);
1557 _key = is_negative ? -_key : _key;
1558 } else if constexpr(std::same_as<const char*, key_type>) {
1559 _key = reinterpret_cast<const char*>(c.as_p());
1560 M_insist(not is_negative, "string cannot be negative");
1561 }
1562
1563 std::optional<typename sql_type::primitive_type> key;
1565 if constexpr (std::same_as<sql_type, NChar>) {
1566 key.emplace(CodeGenContext::Get().get_literal_address(_key));
1567 } else {
1568 key.emplace(_key);
1569 }
1571 /* If we materialize before calling the bound functions, the key parameter is independent of the bounds.
1572 * As a result, queries that only differ in the filter predicate are compiled to the exact same
1573 * WebAssembly code, enabling caching of compiled plans in V8. */
1574 if constexpr (std::same_as<sql_type, NChar>) {
1575 uint32_t *key_address = Module::Allocator().raw_malloc<uint32_t>();
1576 *key_address = CodeGenContext::Get().get_literal_raw_address(_key);
1577
1578 Ptr<U32x1> key_ptr(key_address);
1579 key.emplace(U32x1(*key_ptr).to<char*>());
1580 } else {
1581 auto *key_address = Module::Allocator().raw_malloc<key_type>();
1582 *key_address = _key;
1583
1584 Ptr<typename sql_type::primitive_type> key_ptr(key_address);
1585 key.emplace(*key_ptr);
1586 }
1587 } else {
1588 M_unreachable("unknown materialization strategy");
1589 }
1590 M_insist(bool(key), "key must be set");
1591 return Module::Get().emit_call<uint32_t>(
1592 /* fn= */ is_lower_bound ? lower_bound_fn : upper_bound_fn,
1593 /* index_id= */ index_id.clone(),
1594 /* key= */ *key
1595 );
1596 };
1597 Var<U32x1> lo(bool(bounds.lo) ? compile_bound_lookup(bounds.lo->get(), bounds.is_inclusive_lo)
1598 : U32x1(0));
1599 const Var<U32x1> hi(bool(bounds.hi) ? compile_bound_lookup(bounds.hi->get(), not bounds.is_inclusive_hi)
1600 : U32x1(index.num_entries()));
1601 Wasm_insist(lo <= hi, "bounds need to be valid");
1602
1603 /*----- Allocate memory for communication to host. -----*/
1604 M_insist(std::in_range<uint32_t>(M.batch_size), "should fit in uint32_t");
1605
1606 /* Determine alloc size as minimum of number of results and command-line parameter batch size, where 0 is
1607 * interpreted as infinity. */
1608 const Var<U32x1> alloc_size([&](){
1609 U32x1 num_results = hi - lo;
1610 U32x1 num_results_cpy = num_results.clone();
1611 U32x1 batch_size = M.batch_size == 0 ? num_results.clone() : U32x1(M.batch_size);
1612 U32x1 batch_size_cpy = batch_size.clone();
1613 return Select(batch_size < num_results, batch_size_cpy, num_results_cpy);
1614 }());
1615 Ptr<U32x1> buffer_address = Module::Allocator().malloc<uint32_t>(alloc_size);
1616
1617 /*----- Emit setup code *after* allocating memory to guarantee sequential memory allocation for pipeline. -----*/
1618 setup();
1619
1620 /*----- Emit loop code. -----*/
1621 Var<U32x1> num_tuples_in_batch;
1622 Var<Ptr<U32x1>> ptr;
1623 WHILE (lo < hi) {
1624 num_tuples_in_batch = Select(hi - lo > alloc_size, alloc_size, hi - lo);
1625 /* Call host to fill buffer memory with next batch of tuple ids. */
1626 Module::Get().emit_call<void>(
1627 /* fn= */ scan_fn,
1628 /* index_id= */ index_id,
1629 /* entry_offset= */ lo.val(),
1630 /* address= */ buffer_address.clone(),
1631 /* batch_size= */ num_tuples_in_batch.val()
1632 );
1633 lo += num_tuples_in_batch;
1634 ptr = buffer_address.clone();
1635 WHILE (num_tuples_in_batch > 0U) {
1636 static Schema empty_schema;
1638 /* tuple_value_schema= */ M.scan.schema(),
1639 /* tuple_address_schema= */ empty_schema,
1640 /* base_address= */ get_base_address(table_name),
1641 /* layout= */ M.scan.store().table().layout(),
1642 /* layout_schema= */ M.scan.store().table().schema(M.scan.alias()),
1643 /* tuple_id= */ *ptr
1644 );
1645 pipeline();
1646 num_tuples_in_batch -= 1U;
1647 ptr += 1;
1648 }
1649 }
1650
1651 /*----- Emit teardown code. -----*/
1652 teardown();
1653
1654 /*----- Free buffer memory. -----*/
1655 IF (alloc_size > U32x1(0)) { // only free if actually allocated
1656 Module::Allocator().free(buffer_address, alloc_size);
1657 };
1659 /*----- Use binary search to query the index for lo and hi bounds. -----*/
1660 auto compile_bound_lookup = [&](const ast::Expr &bound, bool is_lower_bound) {
1661 auto [constant, is_negative] = get_valid_bound(bound);
1662 auto c = Interpreter::eval(constant);
1663 key_type _key;
1664 if constexpr(m::boolean<key_type>) {
1665 _key = bool(c);
1666 M_insist(not is_negative, "boolean cannot be negative");
1667 } else if constexpr(m::integral<key_type>) {
1668 auto i64 = int64_t(c);
1669 M_insist(std::in_range<key_type>(i64), "integral constant must be in range");
1670 _key = key_type(i64);
1671 _key = is_negative ? -_key : _key;
1672 } else if constexpr(std::same_as<float, key_type>) {
1673 auto d = double(c);
1674 _key = key_type(d);
1675 M_insist(_key == d, "downcasting should not impact precision");
1676 _key = is_negative ? -_key : _key;
1677 } else if constexpr(std::same_as<double, key_type>) {
1678 _key = double(c);
1679 _key = is_negative ? -_key : _key;
1680 } else if constexpr(std::same_as<const char*, key_type>) {
1681 _key = reinterpret_cast<const char*>(c.as_p());
1682 M_insist(not is_negative, "string cannot be negative");
1683 }
1684
1685 std::optional<typename sql_type::primitive_type> key;
1687 if constexpr (std::same_as<sql_type, NChar>) {
1688 key.emplace(CodeGenContext::Get().get_literal_address(_key));
1689 } else {
1690 key.emplace(_key);
1691 }
1693 /* If we materialize before calling the bound functions, the key parameter is independent of the bounds.
1694 * As a result, queries that only differ in the filter predicate are compiled to the exact same
1695 * WebAssembly code, enabling caching of compiled plans in V8. */
1696 if constexpr (std::same_as<sql_type, NChar>) {
1697 uint32_t *key_address = Module::Allocator().raw_malloc<uint32_t>();
1698 *key_address = CodeGenContext::Get().get_literal_raw_address(_key);
1699
1700 Ptr<U32x1> key_ptr(key_address);
1701 key.emplace(U32x1(*key_ptr).to<char*>());
1702 } else {
1703 auto *key_address = Module::Allocator().raw_malloc<key_type>();
1704 *key_address = _key;
1705
1706 Ptr<typename sql_type::primitive_type> key_ptr(key_address);
1707 key.emplace(*key_ptr);
1708 }
1709 } else {
1710 M_unreachable("unknown materialization strategy");
1711 }
1712 M_insist(bool(key), "key must be set");
1713
1714 /* Implementation based on https://en.cppreference.com/w/cpp/algorithm/lower_bound and
1715 * https://en.cppreference.com/w/cpp/algorithm/upper_bound. */
1716 Var<Ptr<void>> first(get_array_index_base_address(table_name, attr_name));
1717 Var<U32x1> count(get_array_index_num_entries(table_name, attr_name));
1718
1719 WHILE (count > 0U) {
1720 const Var<U32x1> step(count / 2U);
1721 const Var<Ptr<void>> it(first + (step * entry_size).make_signed());
1722 Boolx1 cond = M_CONSTEXPR_COND(
1723 std::same_as<M_COMMA(sql_type) NChar>,
1724 strcmp(
1725 NChar(it.to<char*>(), false, as<const CharacterSequence>(bound.type())),
1726 NChar(*key, false, as<const CharacterSequence>(bound.type())),
1727 is_lower_bound ? LT : LE
1728 ).insist_not_null(),
1730 std::same_as<M_COMMA(key_type) bool>,
1731 is_lower_bound ? Boolx1(*it.to<bool*>()).to<uint8_t>() < (*key).template to<uint8_t>()
1732 : Boolx1(*it.to<bool*>()).to<uint8_t>() <= (*key).template to<uint8_t>(),
1733 is_lower_bound ? *it.to<key_type*>() < *key : *it.to<key_type*>() <= *key
1734 )
1735 );
1736 IF (cond) {
1737 first = it + entry_size;
1738 count -= step + 1U;
1739 } ELSE {
1740 count = step;
1741 };
1742 }
1743 return first;
1744 };
1745 Var<Ptr<void>> lo(
1746 bool(bounds.lo) ? compile_bound_lookup(bounds.lo->get(), bounds.is_inclusive_lo)
1747 : get_array_index_base_address(table_name, attr_name)
1748 );
1749 const Var<Ptr<void>> hi(
1750 bool(bounds.hi) ? compile_bound_lookup(bounds.hi->get(), not bounds.is_inclusive_hi)
1751 : get_array_index_base_address(table_name, attr_name)
1752 + (get_array_index_num_entries(table_name, attr_name) * entry_size).make_signed()
1753 );
1754 Wasm_insist(lo <= hi, "bounds need to be valid");
1755
1756 /*----- Emit setup code *after* allocating memory to guarantee sequential memory allocation for pipeline. -----*/
1757 setup();
1758
1759 /*----- Emit loop code. -----*/
1760 WHILE (lo < hi) {
1761 constexpr int32_t value_offset =
1762 ((sizeof(key_type) + alignof(value_type) - 1U) / alignof(value_type)) * alignof(value_type);
1763 U32x1 tuple_id = PrimitiveExpr<value_type>(*(lo + value_offset).to<value_type*>()).template to<uint32_t>();
1764 static Schema empty_schema;
1766 /* tuple_value_schema= */ M.scan.schema(),
1767 /* tuple_address_schema= */ empty_schema,
1768 /* base_address= */ get_base_address(table_name),
1769 /* layout= */ M.scan.store().table().layout(),
1770 /* layout_schema= */ M.scan.store().table().schema(M.scan.alias()),
1771 /* tuple_id= */ tuple_id
1772 );
1773 pipeline();
1774 lo += int32_t(entry_size);
1775 }
1776
1777 /*----- Emit teardown code. -----*/
1778 teardown();
1779 } else {
1780 M_unreachable("unknown compilation strategy");
1781 }
1782}
1783
1784template<idx::IndexMethod IndexMethod, typename Index>
1785void index_scan_codegen_interpretation(const Index &index, const index_scan_bounds_t &bounds,
1786 const Match<IndexScan<IndexMethod>> &M,
1787 setup_t setup, pipeline_t pipeline, teardown_t teardown)
1788{
1789 using key_type = Index::key_type;
1790
1791 static Schema empty_schema;
1792
1793 /*----- Interpret lo and hi bounds to retrieve index scan range -----*/
1794 auto interpret_and_lookup_bound = [&](const ast::Expr &bound, bool is_lower_bound) -> std::size_t {
1795 auto [constant, is_negative] = get_valid_bound(bound);
1796 auto c = Interpreter::eval(constant);
1797 key_type key;
1798 if constexpr(m::boolean<key_type>) {
1799 key = bool(c);
1800 M_insist(not is_negative, "boolean cannot be negative");
1801 } else if constexpr(m::integral<key_type>) {
1802 auto i64 = int64_t(c);
1803 M_insist(std::in_range<key_type>(i64), "integral constant must be in range");
1804 key = key_type(i64);
1805 key = is_negative ? -key : key;
1806 } else if constexpr(std::same_as<float, key_type>) {
1807 auto d = double(c);
1808 key = key_type(d);
1809 M_insist(key == d, "downcasting should not impact precision");
1810 key = is_negative ? -key : key;
1811 } else if constexpr(std::same_as<double, key_type>) {
1812 key = double(c);
1813 key = is_negative ? -key : key;
1814 } else if constexpr(std::same_as<const char*, key_type>) {
1815 key = reinterpret_cast<const char*>(c.as_p());
1816 M_insist(not is_negative, "string cannot be negative");
1817 }
1818 return std::distance(index.begin(), is_lower_bound ? index.lower_bound(key)
1819 : index.upper_bound(key));
1820 };
1821 std::size_t lo = bool(bounds.lo) ? interpret_and_lookup_bound(bounds.lo->get(), bounds.is_inclusive_lo)
1822 : 0UL;
1823 std::size_t hi = bool(bounds.hi) ? interpret_and_lookup_bound(bounds.hi->get(), not bounds.is_inclusive_hi)
1824 : index.num_entries();
1825 M_insist(lo <= hi, "bounds need to be valid");
1826
1828 /*----- Allocate sufficient memory for results. -----*/
1829 uint32_t num_results = hi - lo;
1830 uint32_t *buffer_address = Module::Allocator().raw_malloc<uint32_t>(num_results + 1); // +1 for storing number of results itself
1831
1832 /*----- Perform index scan and fill memory with number of results and results. -----*/
1833 uint32_t *buffer_ptr = buffer_address;
1834 *buffer_ptr = num_results; // store in memory to enable caching
1835 ++buffer_ptr;
1836 for (auto it = index.begin() + lo; it != index.begin() + hi; ++it) {
1837 M_insist(std::in_range<uint32_t>(it->second), "tuple id must fit in uint32_t");
1838 *buffer_ptr = it->second;
1839 ++buffer_ptr;
1840 }
1841
1842 /*----- Emit setup code *after* allocating memory to guarantee sequential memory allocation for pipeline. -----*/
1843 setup();
1844
1845 /*----- Emit loop code. -----*/
1846 Ptr<U32x1> base(buffer_address + 1); // +1 to skip stored number of results
1847 Var<Ptr<U32x1>> ptr(base.clone());
1848 const Var<Ptr<U32x1>> end(base + U32x1(*Ptr<U32x1>(buffer_address)).make_signed());
1849 WHILE (ptr < end) {
1851 /* tuple_value_schema= */ M.scan.schema(),
1852 /* tuple_address_schema= */ empty_schema,
1853 /* base_address= */ get_base_address(M.scan.store().table().name()),
1854 /* layout= */ M.scan.store().table().layout(),
1855 /* layout_schema= */ M.scan.store().table().schema(M.scan.alias()),
1856 /* tuple_id= */ *ptr
1857 );
1858 pipeline();
1859 ptr += 1;
1860 }
1861
1862 /*----- Emit teardown code. -----*/
1863 teardown();
1865 /*----- Define function that emits code for loading and executing pipeline for a single tuple. -----*/
1866 FUNCTION(index_scan_parent_pipeline, void(uint32_t))
1867 {
1868 auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
1869
1870 /*----- Emit setup code. -----*/
1871 setup();
1872
1873 /*----- Load tuple. ----- */
1875 /* tuple_value_schema= */ M.scan.schema(),
1876 /* tuple_address_schema= */ empty_schema,
1877 /* base_address= */ get_base_address(M.scan.store().table().name()),
1878 /* layout= */ M.scan.store().table().layout(),
1879 /* layout_schema= */ M.scan.store().table().schema(M.scan.alias()),
1880 /* tuple_id= */ PARAMETER(0)
1881 );
1882
1883 /*----- Emit pipeline code. -----*/
1884 pipeline();
1885
1886 /*----- Emit teardown code. -----*/
1887 teardown();
1888 }
1889
1890 /*----- Perform index sequential scan, emit code to execute pipeline for each tuple. -----*/
1891 for (auto it = index.begin() + lo; it != index.begin() + hi; ++it) {
1892 M_insist(std::in_range<uint32_t>(it->second), "tuple id must fit in uint32_t");
1893 index_scan_parent_pipeline(uint32_t(it->second));
1894 }
1895 } else {
1896 M_unreachable("unknown materialization strategy");
1897 }
1898}
1899
1900template<idx::IndexMethod IndexMethod, typename Index, sql_type SqlT>
1901void index_scan_codegen_hybrid(const Index &index, const index_scan_bounds_t &bounds,
1902 const Match<IndexScan<IndexMethod>> &M,
1903 setup_t setup, pipeline_t pipeline, teardown_t teardown)
1904{
1905 using key_type = Index::key_type;
1906 using value_type = Index::value_type;
1907 constexpr uint32_t entry_size = sizeof(typename Index::entry_type);
1908 using sql_type = SqlT;
1909
1910 auto table_name = M.scan.store().table().name();
1911 auto attr_name = bounds.attribute.id.name;
1912
1913 /*----- Resolve callback function name. -----*/
1914 const char *scan_fn;
1915#define SET_CALLBACK_FNS(INDEX, KEY) \
1916 scan_fn = M_STR(idx_scan_##INDEX##_##KEY)
1917
1918#define RESOLVE_KEYTYPE(INDEX) \
1919 if constexpr(std::same_as<SqlT, _Boolx1>) { \
1920 SET_CALLBACK_FNS(INDEX, b); \
1921 } else if constexpr(std::same_as<sql_type, _I8x1>) { \
1922 SET_CALLBACK_FNS(INDEX, i1); \
1923 } else if constexpr(std::same_as<sql_type, _I16x1>) { \
1924 SET_CALLBACK_FNS(INDEX, i2); \
1925 } else if constexpr(std::same_as<sql_type, _I32x1>) { \
1926 SET_CALLBACK_FNS(INDEX, i4); \
1927 } else if constexpr(std::same_as<sql_type, _I64x1>) { \
1928 SET_CALLBACK_FNS(INDEX, i8); \
1929 } else if constexpr(std::same_as<sql_type, _Floatx1>) { \
1930 SET_CALLBACK_FNS(INDEX, f); \
1931 } else if constexpr(std::same_as<sql_type, _Doublex1>) { \
1932 SET_CALLBACK_FNS(INDEX, d); \
1933 } else if constexpr(std::same_as<sql_type, NChar>) { \
1934 SET_CALLBACK_FNS(IDNEX, p); \
1935 } else { \
1936 M_unreachable("incompatible SQL type"); \
1937 }
1938 if constexpr(is_specialization<Index, idx::ArrayIndex>) {
1939 RESOLVE_KEYTYPE(array)
1940 } else if constexpr(is_specialization<Index, idx::RecursiveModelIndex>) {
1941 RESOLVE_KEYTYPE(rmi)
1942 } else {
1943 M_unreachable("unknown index type");
1944 }
1945#undef RESOLVE_KEYTYPE
1946#undef SET_CALLBACK_FNS
1947
1948 /*----- Add index to context. -----*/
1950 U64x1 index_id(context.add_index(index));
1951
1952 /*----- Interpret lo and hi bounds to retrieve index scan range -----*/
1953 auto interpret_and_lookup_bound = [&](const ast::Expr &bound, bool is_lower_bound) -> std::size_t {
1954 auto [constant, is_negative] = get_valid_bound(bound);
1955 auto c = Interpreter::eval(constant);
1956 key_type key;
1957 if constexpr(m::boolean<key_type>) {
1958 key = bool(c);
1959 M_insist(not is_negative, "boolean cannot be negative");
1960 } else if constexpr(m::integral<key_type>) {
1961 auto i64 = int64_t(c);
1962 M_insist(std::in_range<key_type>(i64), "integral constant must be in range");
1963 key = key_type(i64);
1964 key = is_negative ? -key : key;
1965 } else if constexpr(std::same_as<float, key_type>) {
1966 auto d = double(c);
1967 key = key_type(d);
1968 M_insist(key == d, "downcasting should not impact precision");
1969 key = is_negative ? -key : key;
1970 } else if constexpr(std::same_as<double, key_type>) {
1971 key = double(c);
1972 key = is_negative ? -key : key;
1973 } else if constexpr(std::same_as<const char*, key_type>) {
1974 key = reinterpret_cast<const char*>(c.as_p());
1975 M_insist(not is_negative, "string cannot be negative");
1976 }
1977 return std::distance(index.begin(), is_lower_bound ? index.lower_bound(key)
1978 : index.upper_bound(key));
1979 };
1980 std::size_t lo = bool(bounds.lo) ? interpret_and_lookup_bound(bounds.lo->get(), bounds.is_inclusive_lo)
1981 : 0UL;
1982 std::size_t hi = bool(bounds.hi) ? interpret_and_lookup_bound(bounds.hi->get(), not bounds.is_inclusive_hi)
1983 : index.num_entries();
1984 M_insist(lo <= hi, "bounds need to be valid");
1985 M_insist(std::in_range<uint32_t>(lo), "should fit in uint32_t");
1986 M_insist(std::in_range<uint32_t>(hi), "should fit in uint32_t");
1987
1988 /*----- Materialize offsets hi and lo. -----*/
1989 std::optional<U32x1> begin;
1990 std::optional<U32x1> end;
1992 begin.emplace(lo);
1993 end.emplace(hi);
1995 /* If we materialize before allocating buffer memory, the addresses are independent of the buffer size. As a
1996 * result, queries that only differ in the filter predicate are compiled to the exact same WebAssembly code,
1997 * enabling caching of compiled plans in V8. */
1998 uint32_t *offset_address = Module::Allocator().raw_malloc<uint32_t>(2);
1999 offset_address[0] = uint32_t(lo);
2000 offset_address[1] = uint32_t(hi);
2001
2002 Ptr<U32x1> offset_ptr(offset_address);
2003 begin.emplace(*offset_ptr.clone());
2004 end.emplace(*(offset_ptr + 1));
2005 } else {
2006 M_unreachable("unknown materialization strategy");
2007 }
2008 M_insist(bool(end), "end must be set");
2009
2011 /*----- Allocate buffer memory for communication to host. -----*/
2012 M_insist(std::in_range<uint32_t>(M.batch_size), "should fit in uint32_t");
2013
2014 /* Determine alloc size as minimum of number of results and command-line parameter batch size, where 0 is
2015 * interpreted as infinity. */
2016 const Var<U32x1> alloc_size([&](){
2017 U32x1 num_results = end->clone() - begin->clone();
2018 U32x1 num_results_cpy = num_results.clone();
2019 U32x1 batch_size = M.batch_size == 0 ? num_results.clone() : U32x1(M.batch_size);
2020 U32x1 batch_size_cpy = batch_size.clone();
2021 return Select(batch_size < num_results, batch_size_cpy, num_results_cpy);
2022 }());
2023 Ptr<U32x1> buffer_address = Module::Allocator().malloc<uint32_t>(alloc_size);
2024
2025 /*----- Emit setup code *after* allocating memory to guarantee sequential memory allocation for pipeline. -----*/
2026 setup();
2027
2028 /*----- Emit loop code. -----*/
2029 Var<U32x1> num_tuples_in_batch;
2030 Var<Ptr<U32x1>> ptr;
2031 Var<U32x1> _begin(*begin);
2032 WHILE (_begin < end->clone()) {
2033 auto end_cpy = end->clone();
2034 num_tuples_in_batch = Select(*end - _begin > alloc_size, alloc_size, end_cpy - _begin);
2035 /* Call host to fill buffer memory with next batch of tuple ids. */
2036 Module::Get().emit_call<void>(
2037 /* fn= */ scan_fn,
2038 /* index_id= */ index_id,
2039 /* entry_offset= */ _begin.val(),
2040 /* address= */ buffer_address.clone(),
2041 /* batch_size= */ num_tuples_in_batch.val()
2042 );
2043 _begin += num_tuples_in_batch;
2044 ptr = buffer_address.clone();
2045 WHILE (num_tuples_in_batch > 0U) {
2046 static Schema empty_schema;
2048 /* tuple_value_schema= */ M.scan.schema(),
2049 /* tuple_address_schema= */ empty_schema,
2050 /* base_address= */ get_base_address(table_name),
2051 /* layout= */ M.scan.store().table().layout(),
2052 /* layout_schema= */ M.scan.store().table().schema(M.scan.alias()),
2053 /* tuple_id= */ *ptr
2054 );
2055 pipeline();
2056 num_tuples_in_batch -= 1U;
2057 ptr += 1;
2058 }
2059 }
2060
2061 /*----- Emit teardown code. -----*/
2062 teardown();
2063
2064 /*----- Free buffer memory. -----*/
2065 IF (alloc_size > U32x1(0)) { // only free if actually allocated
2066 Module::Allocator().free(buffer_address, alloc_size);
2067 };
2069 /*----- Emit setup code *after* allocating memory to guarantee sequential memory allocation for pipeline. -----*/
2070 setup();
2071
2072 /*----- Emit loop code. -----*/
2073 Var<I32x1> lo((*begin * entry_size).make_signed());
2074 const Var<I32x1> hi((*end * entry_size).make_signed());
2075 WHILE (lo < hi) {
2076 constexpr int32_t value_offset =
2077 ((sizeof(key_type) + alignof(value_type) - 1U) / alignof(value_type)) * alignof(value_type);
2078 U32x1 tuple_id = PrimitiveExpr<value_type>(*(get_array_index_base_address(table_name, attr_name) + lo + value_offset).template to<value_type*>()).template to<uint32_t>();
2079 static Schema empty_schema;
2081 /* tuple_value_schema= */ M.scan.schema(),
2082 /* tuple_address_schema= */ empty_schema,
2083 /* base_address= */ get_base_address(table_name),
2084 /* layout= */ M.scan.store().table().layout(),
2085 /* layout_schema= */ M.scan.store().table().schema(M.scan.alias()),
2086 /* tuple_id= */ tuple_id
2087 );
2088 pipeline();
2089 lo += int32_t(entry_size);
2090 }
2091
2092 /*----- Emit teardown code. -----*/
2093 teardown();
2094
2095 /*----- Discard unused values. ----*/
2096 index_id.discard();
2097 } else {
2098 M_unreachable("unknown compilation strategy");
2099 }
2100}
2101
2103template<idx::IndexMethod IndexMethod, typename Index, sql_type SqlT>
2104void 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)
2105{
2107 index_scan_codegen_compilation<IndexMethod, Index, SqlT>(index, bounds, M, std::move(setup), std::move(pipeline), std::move(teardown));
2109 index_scan_codegen_interpretation<IndexMethod, Index>(index, bounds, M, std::move(setup), std::move(pipeline), std::move(teardown));
2111 index_scan_codegen_hybrid<IndexMethod, Index, SqlT>(index, bounds, M, std::move(setup), std::move(pipeline), std::move(teardown));
2112 } else {
2113 M_unreachable("invalid index access strategy");
2114 }
2115}
2116
2118template<idx::IndexMethod IndexMethod, typename AttrT, sql_type SqlT>
2120 setup_t setup, pipeline_t pipeline, teardown_t teardown)
2121{
2122 /*----- Lookup index. -----*/
2123 Catalog &C = Catalog::Get();
2124 auto &DB = C.get_database_in_use();
2125 auto &table_name = M.scan.store().table().name();
2126 auto &attribute_name = bounds.attribute.id.name;
2127 auto &index_base = DB.get_index(table_name, attribute_name, IndexMethod);
2128
2129 /*----- Resolve index type. -----*/
2130 if constexpr(IndexMethod == idx::IndexMethod::Array and requires { typename idx::ArrayIndex<AttrT>; }) {
2131 auto &index = as<const idx::ArrayIndex<AttrT>>(index_base);
2132 index_scan_resolve_strategy<IndexMethod, const idx::ArrayIndex<AttrT>, SqlT>(
2133 index, bounds, M, std::move(setup), std::move(pipeline), std::move(teardown)
2134 );
2135 } else if constexpr(IndexMethod == idx::IndexMethod::Rmi and requires { typename idx::RecursiveModelIndex<AttrT>; }) {
2136 auto &index = as<const idx::RecursiveModelIndex<AttrT>>(index_base);
2137 index_scan_resolve_strategy<IndexMethod, const idx::RecursiveModelIndex<AttrT>, SqlT>(
2138 index, bounds, M, std::move(setup), std::move(pipeline), std::move(teardown)
2139 );
2140 } else {
2141 M_unreachable("invalid index method");
2142 }
2143}
2144
2146template<idx::IndexMethod IndexMethod>
2148 setup_t setup, pipeline_t pipeline, teardown_t teardown)
2149{
2150 /*----- Extract bounds from CNF. -----*/
2151 auto &cnf = M.filter.filter();
2152 auto bounds = extract_index_scan_bounds(cnf);
2153
2154 /*----- Resolve attribute type. -----*/
2155#define RESOLVE_INDEX_METHOD(ATTRTYPE, SQLTYPE) \
2156 index_scan_resolve_index_method<IndexMethod, ATTRTYPE, SQLTYPE>(bounds, M, std::move(setup), std::move(pipeline), std::move(teardown))
2157
2159 [&](const Boolean&) { RESOLVE_INDEX_METHOD(bool, _Boolx1); },
2160 [&](const Numeric &n) {
2161 switch (n.kind) {
2162 case Numeric::N_Int:
2163 case Numeric::N_Decimal:
2164 switch (n.size()) {
2165 default: M_unreachable("invalid size");
2166 case 8: RESOLVE_INDEX_METHOD(int8_t, _I8x1); break;
2167 case 16: RESOLVE_INDEX_METHOD(int16_t, _I16x1); break;
2168 case 32: RESOLVE_INDEX_METHOD(int32_t, _I32x1); break;
2169 case 64: RESOLVE_INDEX_METHOD(int64_t, _I64x1); break;
2170 }
2171 break;
2172 case Numeric::N_Float:
2173 switch (n.size()) {
2174 default: M_unreachable("invalid size");
2175 case 32: RESOLVE_INDEX_METHOD(float, _Floatx1); break;
2176 case 64: RESOLVE_INDEX_METHOD(double, _Doublex1); break;
2177 }
2178 break;
2179 }
2180 },
2181 [&](const CharacterSequence&) { RESOLVE_INDEX_METHOD(const char*, NChar); },
2182 [&](const Date&) { RESOLVE_INDEX_METHOD(int32_t, _I32x1); },
2183 [&](const DateTime&) { RESOLVE_INDEX_METHOD(int64_t, _I64x1); },
2184 [](auto&&) { M_unreachable("invalid type"); },
2185 }, *bounds.attribute.type);
2186
2187#undef RESOLVE_INDEX_METHOD
2188}
2189
2190template<idx::IndexMethod IndexMethod>
2192 setup_t setup, pipeline_t pipeline, teardown_t teardown)
2193{
2194 auto &schema = M.scan.schema();
2195 M_insist(schema == schema.drop_constants().deduplicate(), "Schema of `ScanOperator` must neither contain NULL nor duplicates");
2196
2197 auto &table = M.scan.store().table();
2198 M_insist(not table.layout().is_finite(), "layout for `wasm::IndexScan` must be infinite");
2199
2206 index_scan_resolve_attribute_type(M, std::move(setup), std::move(pipeline), std::move(teardown));
2207}
2208
2209
2210/*======================================================================================================================
2211 * Filter
2212 *====================================================================================================================*/
2213
2214template<bool Predicated>
2215ConditionSet Filter<Predicated>::pre_condition(std::size_t child_idx, const std::tuple<const FilterOperator*>&)
2216{
2217 M_insist(child_idx == 0);
2218
2219 ConditionSet pre_cond;
2220
2221 if constexpr (not Predicated) {
2222 /*----- Branching filter does not support SIMD. -----*/
2223 pre_cond.add_condition(NoSIMD());
2224 }
2225
2226 return pre_cond;
2227}
2228
2229template<bool Predicated>
2231{
2232 ConditionSet post_cond(post_cond_child);
2233
2234 if constexpr (Predicated) {
2235 /*----- Predicated filter introduces predication. -----*/
2236 post_cond.add_or_replace_condition(m::Predicated(true));
2237 }
2238
2239 return post_cond;
2240}
2241
2242template<bool Predicated>
2244{
2245 const cnf::CNF &cond = M.filter.filter();
2246 const unsigned cost = std::accumulate(cond.cbegin(), cond.cend(), 0U, [](unsigned cost, const cnf::Clause &clause) {
2247 return cost + clause.size();
2248 });
2249 return cost;
2250}
2251
2252template<bool Predicated>
2254{
2255 /*----- Set minimal number of SIMD lanes preferred to get fully utilized SIMD vectors for the filter condition. --*/
2256 CodeGenContext::Get().update_num_simd_lanes_preferred(16); // set own preference
2257
2258 /*----- Execute filter. -----*/
2259 M.child->execute(
2260 /* setup= */ std::move(setup),
2261 /* pipeline= */ [&, pipeline=std::move(pipeline)](){
2262 if constexpr (Predicated) {
2263 CodeGenContext::Get().env().add_predicate(M.filter.filter());
2264 pipeline();
2265 } else {
2266 M_insist(CodeGenContext::Get().num_simd_lanes() == 1, "invalid number of SIMD lanes");
2267 IF (CodeGenContext::Get().env().compile<_Boolx1>(M.filter.filter()).is_true_and_not_null()) {
2268 pipeline();
2269 };
2270 }
2271 },
2272 /* teardown= */ std::move(teardown)
2273 );
2274}
2275
2276
2277/*======================================================================================================================
2278 * LazyDisjunctiveFilter
2279 *====================================================================================================================*/
2280
2281ConditionSet LazyDisjunctiveFilter::pre_condition(std::size_t child_idx, const std::tuple<const FilterOperator*>&)
2282{
2283 M_insist(child_idx == 0);
2284
2285 ConditionSet pre_cond;
2286
2287 /*----- Lazy disjunctive filter does not support SIMD. -----*/
2288 pre_cond.add_condition(NoSIMD());
2289
2290 return pre_cond;
2291}
2292
2294{
2295 const cnf::CNF &cond = M.filter.filter();
2296 M_insist(cond.size() == 1, "disjunctive filter condition must be a single clause");
2297 return cond[0].size() / 2.0; // on avg. half the number of predicates in the clause XXX consider selectivities
2298}
2299
2301 teardown_t teardown)
2302{
2303 const cnf::Clause &clause = M.filter.filter()[0];
2304
2305 M.child->execute(
2306 /* setup= */ std::move(setup),
2307 /* pipeline= */ [&, pipeline=std::move(pipeline)](){
2308 M_insist(CodeGenContext::Get().num_simd_lanes() == 1, "invalid number of SIMD lanes");
2309 BLOCK(lazy_disjunctive_filter)
2310 {
2311 BLOCK(lazy_disjunctive_filter_then)
2312 {
2313 for (const cnf::Predicate &pred : clause) {
2314 auto cond = CodeGenContext::Get().env().compile<_Boolx1>(*pred);
2315 if (pred.negative())
2316 GOTO(cond.is_false_and_not_null(), lazy_disjunctive_filter_then); // break to remainder of pipline
2317 else
2318 GOTO(cond.is_true_and_not_null(), lazy_disjunctive_filter_then); // break to remainder of pipline
2319 }
2320 GOTO(lazy_disjunctive_filter); // skip pipeline
2321 }
2322 pipeline();
2323 }
2324 },
2325 /* teardown= */ std::move(teardown)
2326 );
2327}
2328
2329
2330/*======================================================================================================================
2331 * Projection
2332 *====================================================================================================================*/
2333
2335 std::size_t child_idx,
2336 const std::tuple<const ProjectionOperator*> &partial_inner_nodes)
2337{
2338 M_insist(child_idx == 0);
2339
2340 ConditionSet pre_cond;
2341
2342 auto &projection = *std::get<0>(partial_inner_nodes);
2343
2344 if (not projection.children().empty()) { // projections starting a pipeline produce only a single tuple, i.e. no SIMD
2345 /*----- Projection does only support SIMD if all expressions can be computed using SIMD instructions. -----*/
2346 auto is_simd_computable = [](const ast::Expr &e){
2347 bool simd_computable = true;
2349 [&](const ast::BinaryExpr &b) -> void {
2350 if (b.lhs->type()->is_character_sequence() or b.rhs->type()->is_character_sequence()) {
2351 simd_computable = false; // string operations are not SIMDfiable
2352 throw visit_stop_recursion(); // abort recursion
2353 }
2354 if (b.common_operand_type->is_integral() and b.op().type == TK_SLASH) {
2355 simd_computable = false; // integer division is not SIMDfiable
2356 throw visit_stop_recursion(); // abort recursion
2357 }
2358 if (b.op().type == TK_PERCENT) {
2359 simd_computable = false; // modulo is not SIMDfiable
2360 throw visit_stop_recursion(); // abort recursion
2361 }
2362 },
2363 [](auto&) -> void {
2364 /* designators, constants, unary expressions, NULL(), INT(), already computed aggregates and results
2365 * of a nested query are SIMDfiable; nothing to be done */
2366 },
2368 return simd_computable;
2369 };
2370 auto pred = [&](auto &p){ return not is_simd_computable(p.first); };
2371 if (std::any_of(projection.projections().cbegin(), projection.projections().cend(), pred))
2372 pre_cond.add_condition(NoSIMD());
2373 }
2374
2375 return pre_cond;
2376}
2377
2379{
2380 ConditionSet post_cond(post_cond_child);
2381
2382 /*----- Project and rename in duplicated post condition. -----*/
2383 M_insist(M.projection.projections().size() == M.projection.schema().num_entries(),
2384 "projections must match the operator's schema");
2385 std::vector<std::pair<Schema::Identifier, Schema::Identifier>> old2new;
2386 auto p = M.projection.projections().begin();
2387 for (auto &e: M.projection.schema()) {
2388 auto pred = [&e](const auto &p) { return p.second == e.id; };
2389 if (std::find_if(old2new.cbegin(), old2new.cend(), pred) == old2new.cend()) {
2390 M_insist(p != M.projection.projections().end());
2391 old2new.emplace_back(Schema::Identifier(p->first.get()), e.id);
2392 }
2393 ++p;
2394 }
2395 post_cond.project_and_rename(old2new);
2396
2397 if (not M.child) {
2398 /*----- Leaf projection does not introduce predication. -----*/
2399 post_cond.add_condition(Predicated(false));
2400
2401 /*----- Leaf projection does not introduce SIMD. -----*/
2402 post_cond.add_condition(NoSIMD());
2403 }
2404
2405 return post_cond;
2406}
2407
2408void Projection::execute(const Match<Projection> &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
2409{
2410 auto execute_projection = [&, pipeline=std::move(pipeline)](){
2411 auto &old_env = CodeGenContext::Get().env();
2412 Environment new_env; // fresh environment
2413
2414 /*----- If predication is used, move predicate to newly created environment. -----*/
2415 if (old_env.predicated())
2416 new_env.add_predicate(old_env.extract_predicate());
2417
2418 /*----- Add projections to newly created environment. -----*/
2419 M_insist(M.projection.projections().size() == M.projection.schema().num_entries(),
2420 "projections must match the operator's schema");
2421 auto p = M.projection.projections().begin();
2422 for (auto &e: M.projection.schema()) {
2423 if (not new_env.has(e.id) and not e.id.is_constant()) { // no duplicate and no constant
2424 if (old_env.has(e.id)) {
2425 /*----- Migrate compiled expression to new context. ------*/
2426 new_env.add(e.id, old_env.get(e.id)); // to retain `e.id` for later compilation of expressions
2427 } else {
2428 /*----- Compile expression. -----*/
2429 M_insist(p != M.projection.projections().end());
2430 std::visit(overloaded {
2431 [&]<typename T, std::size_t L>(Expr<T, L> value) -> void {
2432 if (value.can_be_null()) {
2433 Var<Expr<T, L>> var(value); // introduce variable s.t. uses only load from it
2434 new_env.add(e.id, var);
2435 } else {
2436 /* introduce variable w/o NULL bit s.t. uses only load from it */
2437 Var<PrimitiveExpr<T, L>> var(value.insist_not_null());
2438 new_env.add(e.id, Expr<T, L>(var));
2439 }
2440 },
2441 [&](NChar value) -> void {
2442 Var<Ptr<Charx1>> var(value.val()); // introduce variable s.t. uses only load from it
2443 new_env.add(e.id, NChar(var, value.can_be_null(), value.length(),
2444 value.guarantees_terminating_nul()));
2445 },
2446 [](std::monostate) -> void { M_unreachable("invalid expression"); },
2447 }, old_env.compile(p->first));
2448 }
2449 }
2450 ++p;
2451 }
2452
2453 /*----- Resume pipeline with newly created environment. -----*/
2454 {
2455 auto S = CodeGenContext::Get().scoped_environment(std::move(new_env));
2456 pipeline();
2457 }
2458 };
2459
2460 if (M.child) {
2461 /*----- Set minimal number of SIMD lanes preferred to get fully utilized SIMD vectors *after* the projection. */
2462 uint64_t min_size_in_bytes = 16;
2463 for (auto &p : M.projection.projections()) {
2465 [](const m::ast::ErrorExpr&) -> void { M_unreachable("no errors at this stage"); },
2466 [](const m::ast::Designator&) -> void { /* nothing to be done */ },
2467 [](const m::ast::Constant&) -> void { /* nothing to be done */ },
2468 [](const m::ast::QueryExpr&) -> void { /* nothing to be done */ },
2469 [&min_size_in_bytes](const m::ast::FnApplicationExpr &fn) -> void {
2470 if (fn.get_function().is_aggregate())
2471 throw visit_skip_subtree(); // skip arguments to already computed aggregate
2472 min_size_in_bytes = std::min(min_size_in_bytes, (fn.type()->size() + 7) / 8);
2473 if (min_size_in_bytes == 1)
2474 throw visit_stop_recursion(); // abort recursion
2475 },
2476 [&min_size_in_bytes](auto &e) -> void { // i.e. for unary and binary expressions
2477 min_size_in_bytes = std::min(min_size_in_bytes, (e.type()->size() + 7) / 8);
2478 if (min_size_in_bytes == 1)
2479 throw visit_stop_recursion(); // abort recursion
2480 }
2481 }, p.first.get(), m::tag<m::ast::ConstPreOrderExprVisitor>());
2482 }
2483 CodeGenContext::Get().update_num_simd_lanes_preferred(16 / min_size_in_bytes); // set own preference
2484
2485 /*----- Execute projection. -----*/
2486 M.child->get()->execute(std::move(setup), std::move(execute_projection), std::move(teardown));
2487 } else {
2488 /*----- Execute projection. -----*/
2489 setup();
2490 CodeGenContext::Get().set_num_simd_lanes(1); // since only a single tuple is produced
2491 execute_projection();
2492 teardown();
2493 }
2494}
2495
2496
2497/*======================================================================================================================
2498 * Grouping
2499 *====================================================================================================================*/
2500
2501ConditionSet HashBasedGrouping::pre_condition(std::size_t child_idx, const std::tuple<const GroupingOperator*>&)
2502{
2503 M_insist(child_idx == 0);
2504
2505 ConditionSet pre_cond;
2506
2507 /*----- Hash-based grouping does not support SIMD. -----*/
2508 pre_cond.add_condition(NoSIMD());
2509
2510 return pre_cond;
2511}
2512
2514{
2515 return 1.5 * M.child->get_matched_root().info().estimated_cardinality;
2516}
2517
2519{
2520 ConditionSet post_cond;
2521
2522 /*----- Hash-based grouping does not introduce predication (it is already handled by the hash table). -----*/
2523 post_cond.add_condition(Predicated(false));
2524
2525 /*----- Hash-based grouping does not introduce SIMD. -----*/
2526 post_cond.add_condition(NoSIMD());
2527
2528 return post_cond;
2529}
2530
2532 teardown_t teardown)
2533{
2534 // TODO: determine setup
2535 const uint64_t AGGREGATES_SIZE_THRESHOLD_IN_BITS =
2536 M.use_in_place_values ? std::numeric_limits<uint64_t>::max() : 0;
2537
2538 const auto num_keys = M.grouping.group_by().size();
2539
2540 /*----- Compute hash table schema and information about aggregates, especially AVG aggregates. -----*/
2541 Schema ht_schema;
2542 /* Add key(s). */
2543 for (std::size_t i = 0; i < num_keys; ++i) {
2544 auto &e = M.grouping.schema()[i];
2545 ht_schema.add(e.id, e.type, e.constraints);
2546 }
2547 /* Add payload. */
2548 auto p = compute_aggregate_info(M.grouping.aggregates(), M.grouping.schema(), num_keys);
2549 const auto &aggregates = p.first;
2550 const auto &avg_aggregates = p.second;
2551 uint64_t aggregates_size_in_bits = 0;
2552 for (auto &info : aggregates) {
2553 ht_schema.add(info.entry);
2554 aggregates_size_in_bits += info.entry.type->size();
2555 }
2556
2557 /*----- Compute initial capacity of hash table. -----*/
2558 uint32_t initial_capacity = compute_initial_ht_capacity(M.grouping, M.load_factor);
2559
2560 /*----- Create hash table. -----*/
2561 std::unique_ptr<HashTable> ht;
2562 std::vector<HashTable::index_t> key_indices(num_keys);
2563 std::iota(key_indices.begin(), key_indices.end(), 0);
2564 if (M.use_open_addressing_hashing) {
2565 if (aggregates_size_in_bits < AGGREGATES_SIZE_THRESHOLD_IN_BITS)
2566 ht = std::make_unique<GlobalOpenAddressingInPlaceHashTable>(ht_schema, std::move(key_indices),
2567 initial_capacity);
2568 else
2569 ht = std::make_unique<GlobalOpenAddressingOutOfPlaceHashTable>(ht_schema, std::move(key_indices),
2570 initial_capacity);
2571 if (M.use_quadratic_probing)
2572 as<OpenAddressingHashTableBase>(*ht).set_probing_strategy<QuadraticProbing>();
2573 else
2574 as<OpenAddressingHashTableBase>(*ht).set_probing_strategy<LinearProbing>();
2575 } else {
2576 ht = std::make_unique<GlobalChainedHashTable>(ht_schema, std::move(key_indices), initial_capacity);
2577 }
2578
2579 /*----- Create child function. -----*/
2580 FUNCTION(hash_based_grouping_child_pipeline, void(void)) // create function for pipeline
2581 {
2582 auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
2583
2584 std::optional<HashTable::entry_t> dummy;
2585
2586 M.child->execute(
2587 /* setup= */ setup_t::Make_Without_Parent([&](){
2588 ht->setup();
2589 ht->set_high_watermark(M.load_factor);
2590 dummy.emplace(ht->dummy_entry()); // create dummy slot to ignore NULL values in aggregate computations
2591 }),
2592 /* pipeline= */ [&](){
2593 M_insist(bool(dummy));
2594 const auto &env = CodeGenContext::Get().env();
2595
2596 /*----- Insert key if not yet done. -----*/
2597 std::vector<SQL_t> key;
2598 for (auto &p : M.grouping.group_by())
2599 key.emplace_back(env.compile(p.first.get()));
2600 auto [entry, inserted] = ht->try_emplace(std::move(key));
2601
2602 /*----- Compute aggregates. -----*/
2603 Block init_aggs("hash_based_grouping.init_aggs", false),
2604 update_aggs("hash_based_grouping.update_aggs", false),
2605 update_avg_aggs("hash_based_grouping.update_avg_aggs", false);
2606 for (auto &info : aggregates) {
2607 bool is_min = false;
2608 switch (info.fnid) {
2609 default:
2610 M_unreachable("unsupported aggregate function");
2611 case m::Function::FN_MIN:
2612 is_min = true; // set flag and delegate to MAX case
2613 case m::Function::FN_MAX: {
2614 M_insist(info.args.size() == 1,
2615 "MIN and MAX aggregate functions expect exactly one argument");
2616 const auto &arg = *info.args[0];
2617 std::visit(overloaded {
2618 [&]<sql_type _T>(HashTable::reference_t<_T> &&r) -> void
2619 requires (not (std::same_as<_T, _Boolx1> or std::same_as<_T, NChar>)) {
2620 using type = typename _T::type;
2621 using T = PrimitiveExpr<type>;
2622
2623 auto _arg = env.compile(arg);
2624 _T _new_val = convert<_T>(_arg);
2625
2626 BLOCK_OPEN(init_aggs) {
2627 auto [val_, is_null] = _new_val.clone().split();
2628 T val(val_); // due to structured binding and lambda closure
2629 IF (is_null) {
2630 auto neutral = is_min ? T(std::numeric_limits<type>::max())
2631 : T(std::numeric_limits<type>::lowest());
2632 r.clone().set_value(neutral); // initialize with neutral element +inf or -inf
2633 if (info.entry.nullable())
2634 r.clone().set_null(); // first value is NULL
2635 } ELSE {
2636 r.clone().set_value(val); // initialize with first value
2637 if (info.entry.nullable())
2638 r.clone().set_not_null(); // first value is not NULL
2639 };
2640 }
2641 BLOCK_OPEN(update_aggs) {
2642 if (_new_val.can_be_null()) {
2644 auto [new_val_, new_val_is_null_] = _new_val.split();
2645 auto [old_min_max_, old_min_max_is_null] = _T(r.clone()).split();
2646 const Var<Boolx1> new_val_is_null(new_val_is_null_); // due to multiple uses
2647
2648 auto chosen_r = Select(new_val_is_null, dummy->extract<_T>(info.entry.id),
2649 r.clone());
2650 if constexpr (std::floating_point<type>) {
2651 chosen_r.set_value(
2652 is_min ? min(old_min_max_, new_val_) // update old min with new value
2653 : max(old_min_max_, new_val_) // update old max with new value
2654 ); // if new value is NULL, only dummy is written
2655 } else {
2656 const Var<T> new_val(new_val_),
2657 old_min_max(old_min_max_); // due to multiple uses
2658 auto cmp = is_min ? new_val < old_min_max : new_val > old_min_max;
2659#if 1
2660 chosen_r.set_value(
2661 Select(cmp,
2662 new_val, // update to new value
2663 old_min_max) // do not update
2664 ); // if new value is NULL, only dummy is written
2665#else
2666 IF (cmp) {
2667 r.set_value(new_val);
2668 };
2669#endif
2670 }
2671 r.set_null_bit(
2672 old_min_max_is_null and new_val_is_null // MIN/MAX is NULL iff all values are NULL
2673 );
2674 } else {
2675 auto new_val_ = _new_val.insist_not_null();
2676 auto old_min_max_ = _T(r.clone()).insist_not_null();
2677 if constexpr (std::floating_point<type>) {
2678 r.set_value(
2679 is_min ? min(old_min_max_, new_val_) // update old min with new value
2680 : max(old_min_max_, new_val_) // update old max with new value
2681 );
2682 } else {
2683 const Var<T> new_val(new_val_),
2684 old_min_max(old_min_max_); // due to multiple uses
2685 auto cmp = is_min ? new_val < old_min_max : new_val > old_min_max;
2686#if 1
2687 r.set_value(
2688 Select(cmp,
2689 new_val, // update to new value
2690 old_min_max) // do not update
2691 );
2692#else
2693 IF (cmp) {
2694 r.set_value(new_val);
2695 };
2696#endif
2697 }
2698 /* do not update NULL bit since it is already set to `false` */
2699 }
2700 }
2701 },
2702 []<sql_type _T>(HashTable::reference_t<_T>&&) -> void
2703 requires std::same_as<_T,_Boolx1> or std::same_as<_T, NChar> {
2704 M_unreachable("invalid type");
2705 },
2706 [](std::monostate) -> void { M_unreachable("invalid reference"); },
2707 }, entry.extract(info.entry.id));
2708 break;
2709 }
2710 case m::Function::FN_AVG: {
2711 auto it = avg_aggregates.find(info.entry.id);
2712 M_insist(it != avg_aggregates.end());
2713 const auto &avg_info = it->second;
2714 M_insist(avg_info.compute_running_avg,
2715 "AVG aggregate may only occur for running average computations");
2716 M_insist(info.args.size() == 1, "AVG aggregate function expects exactly one argument");
2717 const auto &arg = *info.args[0];
2718
2719 auto r = entry.extract<_Doublex1>(info.entry.id);
2720 auto _arg = env.compile(arg);
2721 _Doublex1 _new_val = convert<_Doublex1>(_arg);
2722
2723 BLOCK_OPEN(init_aggs) {
2724 auto [val_, is_null] = _new_val.clone().split();
2725 Doublex1 val(val_); // due to structured binding and lambda closure
2726 IF (is_null) {
2727 r.clone().set_value(Doublex1(0.0)); // initialize with neutral element 0
2728 if (info.entry.nullable())
2729 r.clone().set_null(); // first value is NULL
2730 } ELSE {
2731 r.clone().set_value(val); // initialize with first value
2732 if (info.entry.nullable())
2733 r.clone().set_not_null(); // first value is not NULL
2734 };
2735 }
2736 BLOCK_OPEN(update_avg_aggs) {
2737 /* Compute AVG as iterative mean as described in Knuth, The Art of Computer Programming
2738 * Vol 2, section 4.2.2. */
2739 if (_new_val.can_be_null()) {
2741 auto [new_val, new_val_is_null_] = _new_val.split();
2742 auto [old_avg_, old_avg_is_null] = _Doublex1(r.clone()).split();
2743 const Var<Boolx1> new_val_is_null(new_val_is_null_); // due to multiple uses
2744 const Var<Doublex1> old_avg(old_avg_); // due to multiple uses
2745
2746 auto delta_absolute = new_val - old_avg;
2747 auto running_count = _I64x1(entry.get<_I64x1>(avg_info.running_count)).insist_not_null();
2748 auto delta_relative = delta_absolute / running_count.to<double>();
2749
2750 auto chosen_r = Select(new_val_is_null, dummy->extract<_Doublex1>(info.entry.id),
2751 r.clone());
2752 chosen_r.set_value(
2753 old_avg + delta_relative // update old average with new value
2754 ); // if new value is NULL, only dummy is written
2755 r.set_null_bit(
2756 old_avg_is_null and new_val_is_null // AVG is NULL iff all values are NULL
2757 );
2758 } else {
2759 auto new_val = _new_val.insist_not_null();
2760 auto old_avg_ = _Doublex1(r.clone()).insist_not_null();
2761 const Var<Doublex1> old_avg(old_avg_); // due to multiple uses
2762
2763 auto delta_absolute = new_val - old_avg;
2764 auto running_count = _I64x1(entry.get<_I64x1>(avg_info.running_count)).insist_not_null();
2765 auto delta_relative = delta_absolute / running_count.to<double>();
2766 r.set_value(
2767 old_avg + delta_relative // update old average with new value
2768 );
2769 /* do not update NULL bit since it is already set to `false` */
2770 }
2771 }
2772 break;
2773 }
2774 case m::Function::FN_SUM: {
2775 M_insist(info.args.size() == 1, "SUM aggregate function expects exactly one argument");
2776 const auto &arg = *info.args[0];
2777 std::visit(overloaded {
2778 [&]<sql_type _T>(HashTable::reference_t<_T> &&r) -> void
2779 requires (not (std::same_as<_T, _Boolx1> or std::same_as<_T, NChar>)) {
2780 using type = typename _T::type;
2781 using T = PrimitiveExpr<type>;
2782
2783 auto _arg = env.compile(arg);
2784 _T _new_val = convert<_T>(_arg);
2785
2786 BLOCK_OPEN(init_aggs) {
2787 auto [val_, is_null] = _new_val.clone().split();
2788 T val(val_); // due to structured binding and lambda closure
2789 IF (is_null) {
2790 r.clone().set_value(T(type(0))); // initialize with neutral element 0
2791 if (info.entry.nullable())
2792 r.clone().set_null(); // first value is NULL
2793 } ELSE {
2794 r.clone().set_value(val); // initialize with first value
2795 if (info.entry.nullable())
2796 r.clone().set_not_null(); // first value is not NULL
2797 };
2798 }
2799 BLOCK_OPEN(update_aggs) {
2800 if (_new_val.can_be_null()) {
2802 auto [new_val, new_val_is_null_] = _new_val.split();
2803 auto [old_sum, old_sum_is_null] = _T(r.clone()).split();
2804 const Var<Boolx1> new_val_is_null(new_val_is_null_); // due to multiple uses
2805
2806 auto chosen_r = Select(new_val_is_null, dummy->extract<_T>(info.entry.id),
2807 r.clone());
2808 chosen_r.set_value(
2809 old_sum + new_val // add new value to old sum
2810 ); // if new value is NULL, only dummy is written
2811 r.set_null_bit(
2812 old_sum_is_null and new_val_is_null // SUM is NULL iff all values are NULL
2813 );
2814 } else {
2815 auto new_val = _new_val.insist_not_null();
2816 auto old_sum = _T(r.clone()).insist_not_null();
2817 r.set_value(
2818 old_sum + new_val // add new value to old sum
2819 );
2820 /* do not update NULL bit since it is already set to `false` */
2821 }
2822 }
2823 },
2824 []<sql_type _T>(HashTable::reference_t<_T>&&) -> void
2825 requires std::same_as<_T,_Boolx1> or std::same_as<_T, NChar> {
2826 M_unreachable("invalid type");
2827 },
2828 [](std::monostate) -> void { M_unreachable("invalid reference"); },
2829 }, entry.extract(info.entry.id));
2830 break;
2831 }
2832 case m::Function::FN_COUNT: {
2833 M_insist(info.args.size() <= 1, "COUNT aggregate function expects at most one argument");
2834
2835 auto r = entry.get<_I64x1>(info.entry.id); // do not extract to be able to access for AVG case
2836
2837 if (info.args.empty()) {
2838 BLOCK_OPEN(init_aggs) {
2839 r.clone() = _I64x1(1); // initialize with 1 (for first value)
2840 }
2841 BLOCK_OPEN(update_aggs) {
2842 auto old_count = _I64x1(r.clone()).insist_not_null();
2843 r.set_value(
2844 old_count + int64_t(1) // increment old count by 1
2845 );
2846 /* do not update NULL bit since it is already set to `false` */
2847 }
2848 } else {
2849 const auto &arg = *info.args[0];
2850
2851 auto _arg = env.compile(arg);
2852 I64x1 new_val_not_null = not_null(_arg).to<int64_t>();
2853
2854 BLOCK_OPEN(init_aggs) {
2855 r.clone() = _I64x1(new_val_not_null.clone()); // initialize with 1 iff first value is present
2856 }
2857 BLOCK_OPEN(update_aggs) {
2858 auto old_count = _I64x1(r.clone()).insist_not_null();
2859 r.set_value(
2860 old_count + new_val_not_null // increment old count by 1 iff new value is present
2861 );
2862 /* do not update NULL bit since it is already set to `false` */
2863 }
2864 }
2865 break;
2866 }
2867 }
2868 }
2869
2870 /*----- If group has been inserted, initialize aggregates. Otherwise, update them. -----*/
2871 IF (inserted) {
2872 init_aggs.attach_to_current();
2873 } ELSE {
2874 update_aggs.attach_to_current();
2875 update_avg_aggs.attach_to_current(); // after others to ensure that running count is incremented before
2876 };
2877 },
2878 /* teardown= */ teardown_t::Make_Without_Parent([&](){ ht->teardown(); })
2879 );
2880 }
2881 hash_based_grouping_child_pipeline(); // call child function
2882
2883 auto &env = CodeGenContext::Get().env();
2884
2885 /*----- Process each computed group. -----*/
2886 setup_t(std::move(setup), [&](){ ht->setup(); })();
2887 ht->for_each([&, pipeline=std::move(pipeline)](HashTable::const_entry_t entry){
2888 /*----- Compute key schema to detect duplicated keys. -----*/
2889 Schema key_schema;
2890 for (std::size_t i = 0; i < num_keys; ++i) {
2891 auto &e = M.grouping.schema()[i];
2892 key_schema.add(e.id, e.type, e.constraints);
2893 }
2894
2895 /*----- Add computed group tuples to current environment. ----*/
2896 for (auto &e : M.grouping.schema().deduplicate()) {
2897 try {
2898 key_schema.find(e.id);
2899 } catch (invalid_argument&) {
2900 continue; // skip duplicated keys since they must not be used afterwards
2901 }
2902
2903 if (auto it = avg_aggregates.find(e.id);
2904 it != avg_aggregates.end() and not it->second.compute_running_avg)
2905 { // AVG aggregates which is not yet computed, divide computed sum with computed count
2906 auto &avg_info = it->second;
2907 auto sum = std::visit(overloaded {
2908 [&]<sql_type T>(HashTable::const_reference_t<T> &&r) -> _Doublex1
2909 requires (std::same_as<T, _I64x1> or std::same_as<T, _Doublex1>) {
2910 return T(r).template to<double>();
2911 },
2912 [](auto&&) -> _Doublex1 { M_unreachable("invalid type"); },
2913 [](std::monostate&&) -> _Doublex1 { M_unreachable("invalid reference"); },
2914 }, entry.get(avg_info.sum));
2915 auto count = _I64x1(entry.get<_I64x1>(avg_info.running_count)).insist_not_null().to<double>();
2916 auto avg = sum / count;
2917 if (avg.can_be_null()) {
2918 _Var<Doublex1> var(avg); // introduce variable s.t. uses only load from it
2919 env.add(e.id, var);
2920 } else {
2921 /* introduce variable w/o NULL bit s.t. uses only load from it */
2922 Var<Doublex1> var(avg.insist_not_null());
2923 env.add(e.id, _Doublex1(var));
2924 }
2925 } else { // part of key or already computed aggregate
2926 std::visit(overloaded {
2927 [&]<typename T>(HashTable::const_reference_t<Expr<T>> &&r) -> void {
2928 Expr<T> value = r;
2929 if (value.can_be_null()) {
2930 Var<Expr<T>> var(value); // introduce variable s.t. uses only load from it
2931 env.add(e.id, var);
2932 } else {
2933 /* introduce variable w/o NULL bit s.t. uses only load from it */
2934 Var<PrimitiveExpr<T>> var(value.insist_not_null());
2935 env.add(e.id, Expr<T>(var));
2936 }
2937 },
2938 [&](HashTable::const_reference_t<NChar> &&r) -> void {
2939 NChar value(r);
2940 Var<Ptr<Charx1>> var(value.val()); // introduce variable s.t. uses only load from it
2941 env.add(e.id, NChar(var, value.can_be_null(), value.length(),
2942 value.guarantees_terminating_nul()));
2943 },
2944 [](std::monostate&&) -> void { M_unreachable("invalid reference"); },
2945 }, entry.get(e.id)); // do not extract to be able to access for not-yet-computed AVG aggregates
2946 }
2947 }
2948
2949 /*----- Resume pipeline. -----*/
2950 pipeline();
2951 });
2952 teardown_t(std::move(teardown), [&](){ ht->teardown(); })();
2953}
2954
2956 std::size_t child_idx,
2957 const std::tuple<const GroupingOperator*> &partial_inner_nodes)
2958{
2959 M_insist(child_idx == 0);
2960
2961 ConditionSet pre_cond;
2962
2963 /*----- Ordered grouping needs the data sorted on the grouping key (in either order). -----*/
2964 Sortedness::order_t orders;
2965 for (auto &p : std::get<0>(partial_inner_nodes)->group_by()) {
2966 Schema::Identifier id(p.first);
2967 if (orders.find(id) == orders.cend())
2968 orders.add(std::move(id), Sortedness::O_UNDEF);
2969 }
2970 pre_cond.add_condition(Sortedness(std::move(orders)));
2971
2972 /*----- Ordered grouping does not support SIMD. -----*/
2973 pre_cond.add_condition(NoSIMD());
2974
2975 return pre_cond;
2976}
2977
2979{
2980 return 1.0 * M.child->get_matched_root().info().estimated_cardinality;
2981}
2982
2984{
2985 ConditionSet post_cond;
2986
2987 /*----- Ordered grouping does not introduce predication. -----*/
2988 post_cond.add_condition(Predicated(false));
2989
2990 /*----- Preserve order of child for grouping keys. -----*/
2991 Sortedness::order_t orders;
2992 const auto &sortedness_child = post_cond_child.get_condition<Sortedness>();
2993 for (auto &[expr, alias] : M.grouping.group_by()) {
2994 auto it = sortedness_child.orders().find(Schema::Identifier(expr));
2995 M_insist(it != sortedness_child.orders().cend());
2996 Schema::Identifier id = alias.has_value() ? Schema::Identifier(alias.assert_not_none())
2998 if (orders.find(id) == orders.cend())
2999 orders.add(std::move(id), it->second); // drop duplicate since it must not be used afterwards
3000 }
3001 post_cond.add_condition(Sortedness(std::move(orders)));
3002
3003 /*----- Ordered grouping does not introduce SIMD. -----*/
3004 post_cond.add_condition(NoSIMD());
3005
3006 return post_cond;
3007}
3008
3010{
3011 Environment results;
3012 const auto num_keys = M.grouping.group_by().size();
3013
3014 /*----- Compute key schema to detect duplicated keys. -----*/
3015 Schema key_schema;
3016 for (std::size_t i = 0; i < num_keys; ++i) {
3017 auto &e = M.grouping.schema()[i];
3018 key_schema.add(e.id, e.type, e.constraints);
3019 }
3020
3021 /*----- Compute information about aggregates, especially about AVG aggregates. -----*/
3022 auto p = compute_aggregate_info(M.grouping.aggregates(), M.grouping.schema(), num_keys);
3023 const auto &aggregates = p.first;
3024 const auto &avg_aggregates = p.second;
3025
3026 /*----- Forward declare function to emit a group tuple in the current environment and resume the pipeline. -----*/
3027 FunctionProxy<void(void)> emit_group_and_resume_pipeline("emit_group_and_resume_pipeline");
3028
3029 std::optional<Var<Boolx1>> first_iteration;
3031 Global<Boolx1> first_iteration_backup(true);
3032
3033 using agg_t = agg_t_<false>;
3034 using agg_backup_t = agg_t_<true>;
3035 agg_t agg_values[aggregates.size()];
3036 agg_backup_t agg_value_backups[aggregates.size()];
3037
3038 using key_t = key_t_<false>;
3039 using key_backup_t = key_t_<true>;
3040 key_t key_values[num_keys];
3041 key_backup_t key_value_backups[num_keys];
3042
3043 auto store_locals_to_globals = [&](){
3044 /*----- Store local aggregate values to globals to access them in other function. -----*/
3045 for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
3046 auto &info = aggregates[idx];
3047
3048 switch (info.fnid) {
3049 default:
3050 M_unreachable("unsupported aggregate function");
3051 case m::Function::FN_MIN:
3052 case m::Function::FN_MAX: {
3053 auto min_max = [&]<typename T>() {
3054 auto &[min_max, is_null] = *M_notnull((
3055 std::get_if<std::pair<Var<PrimitiveExpr<T>>, std::optional<Var<Boolx1>>>>(&agg_values[idx])
3056 ));
3057 auto &[min_max_backup, is_null_backup] = *M_notnull((
3058 std::get_if<std::pair<Global<PrimitiveExpr<T>>,
3059 std::optional<Global<Boolx1>>>>(&agg_value_backups[idx])
3060 ));
3061 M_insist(bool(is_null) == bool(is_null_backup));
3062
3063 min_max_backup = min_max;
3064 if (is_null)
3065 *is_null_backup = *is_null;
3066 };
3067 auto &n = as<const Numeric>(*info.entry.type);
3068 switch (n.kind) {
3069 case Numeric::N_Int:
3070 case Numeric::N_Decimal:
3071 switch (n.size()) {
3072 default: M_unreachable("invalid size");
3073 case 8: min_max.template operator()<int8_t >(); break;
3074 case 16: min_max.template operator()<int16_t>(); break;
3075 case 32: min_max.template operator()<int32_t>(); break;
3076 case 64: min_max.template operator()<int64_t>(); break;
3077 }
3078 break;
3079 case Numeric::N_Float:
3080 if (n.size() <= 32)
3081 min_max.template operator()<float>();
3082 else
3083 min_max.template operator()<double>();
3084 }
3085 break;
3086 }
3087 case m::Function::FN_AVG: {
3088 auto &[avg, is_null] = *M_notnull((
3089 std::get_if<std::pair<Var<Doublex1>, std::optional<Var<Boolx1>>>>(&agg_values[idx])
3090 ));
3091 auto &[avg_backup, is_null_backup] = *M_notnull((
3092 std::get_if<std::pair<Global<Doublex1>, std::optional<Global<Boolx1>>>>(&agg_value_backups[idx])
3093 ));
3094 M_insist(bool(is_null) == bool(is_null_backup));
3095
3096 avg_backup = avg;
3097 if (is_null)
3098 *is_null_backup = *is_null;
3099
3100 break;
3101 }
3102 case m::Function::FN_SUM: {
3103 M_insist(info.args.size() == 1, "SUM aggregate function expects exactly one argument");
3104 const auto &arg = *info.args[0];
3105
3106 auto sum = [&]<typename T>() {
3107 auto &[sum, is_null] = *M_notnull((
3108 std::get_if<std::pair<Var<PrimitiveExpr<T>>, std::optional<Var<Boolx1>>>>(&agg_values[idx])
3109 ));
3110 auto &[sum_backup, is_null_backup] = *M_notnull((
3111 std::get_if<std::pair<Global<PrimitiveExpr<T>>,
3112 std::optional<Global<Boolx1>>>>(&agg_value_backups[idx])
3113 ));
3114 M_insist(bool(is_null) == bool(is_null_backup));
3115
3116 sum_backup = sum;
3117 if (is_null)
3118 *is_null_backup = *is_null;
3119 };
3120 auto &n = as<const Numeric>(*info.entry.type);
3121 switch (n.kind) {
3122 case Numeric::N_Int:
3123 case Numeric::N_Decimal:
3124 switch (n.size()) {
3125 default: M_unreachable("invalid size");
3126 case 8: sum.template operator()<int8_t >(); break;
3127 case 16: sum.template operator()<int16_t>(); break;
3128 case 32: sum.template operator()<int32_t>(); break;
3129 case 64: sum.template operator()<int64_t>(); break;
3130 }
3131 break;
3132 case Numeric::N_Float:
3133 if (n.size() <= 32)
3134 sum.template operator()<float>();
3135 else
3136 sum.template operator()<double>();
3137 }
3138 break;
3139 }
3140 case m::Function::FN_COUNT: {
3141 auto &count = *M_notnull(std::get_if<Var<I64x1>>(&agg_values[idx]));
3142 auto &count_backup = *M_notnull(std::get_if<Global<I64x1>>(&agg_value_backups[idx]));
3143
3144 count_backup = count;
3145
3146 break;
3147 }
3148 }
3149 }
3150
3151 /*----- Store local key values to globals to access them in other function. -----*/
3152 auto store = [&]<typename T>(std::size_t idx) {
3153 auto &[key, is_null] = *M_notnull((
3154 std::get_if<std::pair<Var<PrimitiveExpr<T>>, std::optional<Var<Boolx1>>>>(&key_values[idx])
3155 ));
3156 auto &[key_backup, is_null_backup] = *M_notnull((
3157 std::get_if<std::pair<Global<PrimitiveExpr<T>>, std::optional<Global<Boolx1>>>>(&key_value_backups[idx])
3158 ));
3159 M_insist(bool(is_null) == bool(is_null_backup));
3160
3161 key_backup = key;
3162 if (is_null)
3163 *is_null_backup = *is_null;
3164 };
3165 for (std::size_t idx = 0; idx < num_keys; ++idx) {
3167 [&](const Boolean&) { store.template operator()<bool>(idx); },
3168 [&](const Numeric &n) {
3169 switch (n.kind) {
3170 case Numeric::N_Int:
3171 case Numeric::N_Decimal:
3172 switch (n.size()) {
3173 default: M_unreachable("invalid size");
3174 case 8: store.template operator()<int8_t >(idx); break;
3175 case 16: store.template operator()<int16_t>(idx); break;
3176 case 32: store.template operator()<int32_t>(idx); break;
3177 case 64: store.template operator()<int64_t>(idx); break;
3178 }
3179 break;
3180 case Numeric::N_Float:
3181 if (n.size() <= 32)
3182 store.template operator()<float>(idx);
3183 else
3184 store.template operator()<double>(idx);
3185 }
3186 },
3187 [&](const CharacterSequence &cs) {
3188 auto &key = *M_notnull(std::get_if<Var<Ptr<Charx1>>>(&key_values[idx]));
3189 auto &key_backup = *M_notnull(std::get_if<Global<Ptr<Charx1>>>(&key_value_backups[idx]));
3190
3191 key_backup = key;
3192 },
3193 [&](const Date&) { store.template operator()<int32_t>(idx); },
3194 [&](const DateTime&) { store.template operator()<int64_t>(idx); },
3195 [](auto&&) { M_unreachable("invalid type"); },
3196 }, *M.grouping.schema()[idx].type);
3197 }
3198 };
3199
3200 M.child->execute(
3201 /* setup= */ setup_t::Make_Without_Parent([&](){
3202 first_iteration.emplace(first_iteration_backup);
3203
3204 /*----- Initialize aggregates and their backups. -----*/
3205 for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
3206 auto &info = aggregates[idx];
3207 const bool nullable = info.entry.nullable();
3208
3209 bool is_min = false;
3210 switch (info.fnid) {
3211 default:
3212 M_unreachable("unsupported aggregate function");
3213 case m::Function::FN_MIN:
3214 is_min = true; // set flag and delegate to MAX case
3215 case m::Function::FN_MAX: {
3216 auto min_max = [&]<typename T>() {
3217 auto neutral = is_min ? std::numeric_limits<T>::max()
3218 : std::numeric_limits<T>::lowest();
3219
3220 Var<PrimitiveExpr<T>> min_max;
3221 Global<PrimitiveExpr<T>> min_max_backup(neutral); // initialize with neutral element +inf or -inf
3222 std::optional<Var<Boolx1>> is_null;
3223 std::optional<Global<Boolx1>> is_null_backup;
3224
3225 /*----- Set local aggregate variables to global backups. -----*/
3226 min_max = min_max_backup;
3227 if (nullable) {
3228 is_null_backup.emplace(true); // MIN/MAX is initially NULL
3229 is_null.emplace(*is_null_backup);
3230 }
3231
3232 /*----- Add global aggregate to result environment to access it in other function. -----*/
3233 if (nullable)
3234 results.add(info.entry.id, Select(*is_null_backup, Expr<T>::Null(), min_max_backup));
3235 else
3236 results.add(info.entry.id, min_max_backup.val());
3237
3238 /*----- Move aggregate variables to access them later. ----*/
3239 new (&agg_values[idx]) agg_t(std::make_pair(std::move(min_max), std::move(is_null)));
3240 new (&agg_value_backups[idx]) agg_backup_t(std::make_pair(
3241 std::move(min_max_backup), std::move(is_null_backup)
3242 ));
3243 };
3244 auto &n = as<const Numeric>(*info.entry.type);
3245 switch (n.kind) {
3246 case Numeric::N_Int:
3247 case Numeric::N_Decimal:
3248 switch (n.size()) {
3249 default: M_unreachable("invalid size");
3250 case 8: min_max.template operator()<int8_t >(); break;
3251 case 16: min_max.template operator()<int16_t>(); break;
3252 case 32: min_max.template operator()<int32_t>(); break;
3253 case 64: min_max.template operator()<int64_t>(); break;
3254 }
3255 break;
3256 case Numeric::N_Float:
3257 if (n.size() <= 32)
3258 min_max.template operator()<float>();
3259 else
3260 min_max.template operator()<double>();
3261 }
3262 break;
3263 }
3264 case m::Function::FN_AVG: {
3265 Var<Doublex1> avg;
3266 Global<Doublex1> avg_backup(0.0); // initialize with neutral element 0
3267 std::optional<Var<Boolx1>> is_null;
3268 std::optional<Global<Boolx1>> is_null_backup;
3269
3270 /*----- Set local aggregate variables to global backups. -----*/
3271 avg = avg_backup;
3272 if (nullable) {
3273 is_null_backup.emplace(true); // AVG is initially NULL
3274 is_null.emplace(*is_null_backup);
3275 }
3276
3277 /*----- Add global aggregate to result environment to access it in other function. -----*/
3278 if (nullable)
3279 results.add(info.entry.id, Select(*is_null_backup, _Doublex1::Null(), avg_backup));
3280 else
3281 results.add(info.entry.id, avg_backup.val());
3282
3283 /*----- Move aggregate variables to access them later. ----*/
3284 new (&agg_values[idx]) agg_t(std::make_pair(std::move(avg), std::move(is_null)));
3285 new (&agg_value_backups[idx]) agg_backup_t(std::make_pair(
3286 std::move(avg_backup), std::move(is_null_backup)
3287 ));
3288
3289 break;
3290 }
3291 case m::Function::FN_SUM: {
3292 auto sum = [&]<typename T>() {
3294 Global<PrimitiveExpr<T>> sum_backup(T(0)); // initialize with neutral element 0
3295 std::optional<Var<Boolx1>> is_null;
3296 std::optional<Global<Boolx1>> is_null_backup;
3297
3298 /*----- Set local aggregate variables to global backups. -----*/
3299 sum = sum_backup;
3300 if (nullable) {
3301 is_null_backup.emplace(true); // SUM is initially NULL
3302 is_null.emplace(*is_null_backup);
3303 }
3304
3305 /*----- Add global aggregate to result environment to access it in other function. -----*/
3306 if (nullable)
3307 results.add(info.entry.id, Select(*is_null_backup, Expr<T>::Null(), sum_backup));
3308 else
3309 results.add(info.entry.id, sum_backup.val());
3310
3311 /*----- Move aggregate variables to access them later. ----*/
3312 new (&agg_values[idx]) agg_t(std::make_pair(std::move(sum), std::move(is_null)));
3313 new (&agg_value_backups[idx]) agg_backup_t(std::make_pair(
3314 std::move(sum_backup), std::move(is_null_backup)
3315 ));
3316 };
3317 auto &n = as<const Numeric>(*info.entry.type);
3318 switch (n.kind) {
3319 case Numeric::N_Int:
3320 case Numeric::N_Decimal:
3321 switch (n.size()) {
3322 default: M_unreachable("invalid size");
3323 case 8: sum.template operator()<int8_t >(); break;
3324 case 16: sum.template operator()<int16_t>(); break;
3325 case 32: sum.template operator()<int32_t>(); break;
3326 case 64: sum.template operator()<int64_t>(); break;
3327 }
3328 break;
3329 case Numeric::N_Float:
3330 if (n.size() <= 32)
3331 sum.template operator()<float>();
3332 else
3333 sum.template operator()<double>();
3334 }
3335 break;
3336 }
3337 case m::Function::FN_COUNT: {
3338 Var<I64x1> count;
3339 Global<I64x1> count_backup(0); // initialize with neutral element 0
3340 /* no `is_null` variables needed since COUNT will not be NULL */
3341
3342 /*----- Set local aggregate variable to global backup. -----*/
3343 count = count_backup;
3344
3345 /*----- Add global aggregate to result environment to access it in other function. -----*/
3346 results.add(info.entry.id, count_backup.val());
3347
3348 /*----- Move aggregate variables to access them later. ----*/
3349 new (&agg_values[idx]) agg_t(std::move(count));
3350 new (&agg_value_backups[idx]) agg_backup_t(std::move(count_backup));
3351
3352 break;
3353 }
3354 }
3355 }
3356
3357 /*----- Initialize keys and their backups. -----*/
3358 auto init = [&]<typename T>(std::size_t idx) {
3359 const bool nullable = M.grouping.schema()[idx].nullable();
3360
3362 Global<PrimitiveExpr<T>> key_backup;
3363 std::optional<Var<Boolx1>> is_null;
3364 std::optional<Global<Boolx1>> is_null_backup;
3365
3366 /*----- Set local key variables to global backups. -----*/
3367 key = key_backup;
3368 if (nullable) {
3369 is_null_backup.emplace();
3370 is_null.emplace(*is_null_backup);
3371 }
3372
3373 try {
3374 auto id = M.grouping.schema()[idx].id;
3375 key_schema.find(id);
3376
3377 /*----- Add global key to result environment to access it in other function. -----*/
3378 if (nullable)
3379 results.add(id, Select(*is_null_backup, Expr<T>::Null(), key_backup));
3380 else
3381 results.add(id, key_backup.val());
3382 } catch (invalid_argument&) {
3383 /* skip adding to result environment for duplicate keys since they must not be used afterwards */
3384 }
3385
3386 /*----- Move key variables to access them later. ----*/
3387 new (&key_values[idx]) key_t(std::make_pair(std::move(key), std::move(is_null)));
3388 new (&key_value_backups[idx]) key_backup_t(std::make_pair(
3389 std::move(key_backup), std::move(is_null_backup)
3390 ));
3391 };
3392 for (std::size_t idx = 0; idx < num_keys; ++idx) {
3394 [&](const Boolean&) { init.template operator()<bool>(idx); },
3395 [&](const Numeric &n) {
3396 switch (n.kind) {
3397 case Numeric::N_Int:
3398 case Numeric::N_Decimal:
3399 switch (n.size()) {
3400 default: M_unreachable("invalid size");
3401 case 8: init.template operator()<int8_t >(idx); break;
3402 case 16: init.template operator()<int16_t>(idx); break;
3403 case 32: init.template operator()<int32_t>(idx); break;
3404 case 64: init.template operator()<int64_t>(idx); break;
3405 }
3406 break;
3407 case Numeric::N_Float:
3408 if (n.size() <= 32)
3409 init.template operator()<float>(idx);
3410 else
3411 init.template operator()<double>(idx);
3412 }
3413 },
3414 [&](const CharacterSequence &cs) {
3415 Var<Ptr<Charx1>> key;
3416 Global<Ptr<Charx1>> key_backup;
3417 /* no `is_null` variables needed since pointer types must not be NULL */
3418
3419 /*----- Set local key variable to global backup. -----*/
3420 key = key_backup;
3421
3422 try {
3423 auto id = M.grouping.schema()[idx].id;
3424 key_schema.find(id);
3425
3426 /*----- Add global key to result environment to access it in other function. -----*/
3427 NChar str(key_backup.val(), M.grouping.schema()[idx].nullable(), cs.length, cs.is_varying);
3428 results.add(id, std::move(str));
3429 } catch (invalid_argument&) {
3430 /* skip adding to result environment for duplicate keys since they must not be used
3431 * afterwards */
3432 }
3433
3434 /*----- Move key variables to access them later. ----*/
3435 new (&key_values[idx]) key_t(std::move(key));
3436 new (&key_value_backups[idx]) key_backup_t(std::move(key_backup));
3437 },
3438 [&](const Date&) { init.template operator()<int32_t>(idx); },
3439 [&](const DateTime&) { init.template operator()<int64_t>(idx); },
3440 [](auto&&) { M_unreachable("invalid type"); },
3441 }, *M.grouping.schema()[idx].type);
3442 }
3443 }),
3444 /* pipeline= */ [&](){
3445 auto &env = CodeGenContext::Get().env();
3446
3447 /*----- If predication is used, introduce pred. var. and update it before computing aggregates. -----*/
3448 std::optional<Var<Boolx1>> pred;
3449 if (env.predicated()) {
3450 M_insist(CodeGenContext::Get().num_simd_lanes() == 1, "invalid number of SIMD lanes");
3451 pred = env.extract_predicate<_Boolx1>().is_true_and_not_null();
3452 }
3453
3454 /*----- Compute aggregates. -----*/
3455 Block reset_aggs("ordered_grouping.reset_aggs", false),
3456 update_aggs("ordered_grouping.update_aggs", false),
3457 update_avg_aggs("ordered_grouping.update_avg_aggs", false);
3458 for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
3459 auto &info = aggregates[idx];
3460
3461 bool is_min = false;
3462 switch (info.fnid) {
3463 default:
3464 M_unreachable("unsupported aggregate function");
3465 case m::Function::FN_MIN:
3466 is_min = true; // set flag and delegate to MAX case
3467 case m::Function::FN_MAX: {
3468 M_insist(info.args.size() == 1, "MIN and MAX aggregate functions expect exactly one argument");
3469 const auto &arg = *info.args[0];
3470 auto min_max = [&]<typename T>() {
3471 auto neutral = is_min ? std::numeric_limits<T>::max()
3472 : std::numeric_limits<T>::lowest();
3473
3474 auto &[min_max, is_null] = *M_notnull((
3475 std::get_if<std::pair<Var<PrimitiveExpr<T>>, std::optional<Var<Boolx1>>>>(&agg_values[idx])
3476 ));
3477
3478 BLOCK_OPEN(reset_aggs) {
3479 min_max = neutral;
3480 if (is_null)
3481 is_null->set_true();
3482 }
3483
3484 BLOCK_OPEN(update_aggs) {
3485 auto _arg = env.compile(arg);
3486 Expr<T> _new_val = convert<Expr<T>>(_arg);
3487 M_insist(_new_val.can_be_null() == bool(is_null));
3488 if (_new_val.can_be_null()) {
3490 auto _new_val_pred = pred ? Select(*pred, _new_val, Expr<T>::Null()) : _new_val;
3491 auto [new_val_, new_val_is_null_] = _new_val_pred.split();
3492 const Var<Boolx1> new_val_is_null(new_val_is_null_); // due to multiple uses
3493
3494 if constexpr (std::floating_point<T>) {
3495 min_max = Select(new_val_is_null,
3496 min_max, // ignore NULL
3497 is_min ? min(min_max, new_val_) // update old min with new value
3498 : max(min_max, new_val_)); // update old max with new value
3499 } else {
3500 const Var<PrimitiveExpr<T>> new_val(new_val_); // due to multiple uses
3501 auto cmp = is_min ? new_val < min_max : new_val > min_max;
3502#if 1
3503 min_max = Select(new_val_is_null,
3504 min_max, // ignore NULL
3505 Select(cmp,
3506 new_val, // update to new value
3507 min_max)); // do not update
3508#else
3509 IF (not new_val_is_null and cmp) {
3510 min_max = new_val;
3511 };
3512#endif
3513 }
3514 *is_null = *is_null and new_val_is_null; // MIN/MAX is NULL iff all values are NULL
3515 } else {
3516 auto _new_val_pred = pred ? Select(*pred, _new_val, neutral) : _new_val;
3517 auto new_val_ = _new_val_pred.insist_not_null();
3518 if constexpr (std::floating_point<T>) {
3519 min_max = is_min ? min(min_max, new_val_) // update old min with new value
3520 : max(min_max, new_val_); // update old max with new value
3521 } else {
3522 const Var<PrimitiveExpr<T>> new_val(new_val_); // due to multiple uses
3523 auto cmp = is_min ? new_val < min_max : new_val > min_max;
3524#if 1
3525 min_max = Select(cmp,
3526 new_val, // update to new value
3527 min_max); // do not update
3528#else
3529 IF (cmp) {
3530 min_max = new_val;
3531 };
3532#endif
3533 }
3534 }
3535 }
3536 };
3537 auto &n = as<const Numeric>(*info.entry.type);
3538 switch (n.kind) {
3539 case Numeric::N_Int:
3540 case Numeric::N_Decimal:
3541 switch (n.size()) {
3542 default: M_unreachable("invalid size");
3543 case 8: min_max.template operator()<int8_t >(); break;
3544 case 16: min_max.template operator()<int16_t>(); break;
3545 case 32: min_max.template operator()<int32_t>(); break;
3546 case 64: min_max.template operator()<int64_t>(); break;
3547 }
3548 break;
3549 case Numeric::N_Float:
3550 if (n.size() <= 32)
3551 min_max.template operator()<float>();
3552 else
3553 min_max.template operator()<double>();
3554 }
3555 break;
3556 }
3557 case m::Function::FN_AVG:
3558 break; // skip here and handle later
3559 case m::Function::FN_SUM: {
3560 M_insist(info.args.size() == 1, "SUM aggregate function expects exactly one argument");
3561 const auto &arg = *info.args[0];
3562
3563 auto sum = [&]<typename T>() {
3564 auto &[sum, is_null] = *M_notnull((
3565 std::get_if<std::pair<Var<PrimitiveExpr<T>>, std::optional<Var<Boolx1>>>>(&agg_values[idx])
3566 ));
3567
3568 BLOCK_OPEN(reset_aggs) {
3569 sum = T(0);
3570 if (is_null)
3571 is_null->set_true();
3572 }
3573
3574 BLOCK_OPEN(update_aggs) {
3575 auto _arg = env.compile(arg);
3576 Expr<T> _new_val = convert<Expr<T>>(_arg);
3577 M_insist(_new_val.can_be_null() == bool(is_null));
3578 if (_new_val.can_be_null()) {
3580 auto _new_val_pred = pred ? Select(*pred, _new_val, Expr<T>::Null()) : _new_val;
3581 auto [new_val, new_val_is_null_] = _new_val_pred.split();
3582 const Var<Boolx1> new_val_is_null(new_val_is_null_); // due to multiple uses
3583
3584 sum += Select(new_val_is_null,
3585 T(0), // ignore NULL
3586 new_val); // add new value to old sum
3587 *is_null = *is_null and new_val_is_null; // SUM is NULL iff all values are NULL
3588 } else {
3589 auto _new_val_pred = pred ? Select(*pred, _new_val, T(0)) : _new_val;
3590 sum += _new_val_pred.insist_not_null(); // add new value to old sum
3591 }
3592 }
3593 };
3594 auto &n = as<const Numeric>(*info.entry.type);
3595 switch (n.kind) {
3596 case Numeric::N_Int:
3597 case Numeric::N_Decimal:
3598 switch (n.size()) {
3599 default: M_unreachable("invalid size");
3600 case 8: sum.template operator()<int8_t >(); break;
3601 case 16: sum.template operator()<int16_t>(); break;
3602 case 32: sum.template operator()<int32_t>(); break;
3603 case 64: sum.template operator()<int64_t>(); break;
3604 }
3605 break;
3606 case Numeric::N_Float:
3607 if (n.size() <= 32)
3608 sum.template operator()<float>();
3609 else
3610 sum.template operator()<double>();
3611 }
3612 break;
3613 }
3614 case m::Function::FN_COUNT: {
3615 M_insist(info.args.size() <= 1, "COUNT aggregate function expects at most one argument");
3616 M_insist(info.entry.type->is_integral() and info.entry.type->size() == 64);
3617
3618 auto &count = *M_notnull(std::get_if<Var<I64x1>>(&agg_values[idx]));
3619
3620 BLOCK_OPEN(reset_aggs) {
3621 count = int64_t(0);
3622 }
3623
3624 BLOCK_OPEN(update_aggs) {
3625 if (info.args.empty()) {
3626 count += pred ? pred->to<int64_t>() : I64x1(1); // increment old count by 1 iff `pred` is true
3627 } else {
3628 auto _new_val = env.compile(*info.args[0]);
3629 if (can_be_null(_new_val)) {
3631 I64x1 inc = pred ? (not_null(_new_val) and *pred).to<int64_t>()
3632 : not_null(_new_val).to<int64_t>();
3633 count += inc; // increment old count by 1 iff new value is present and `pred` is true
3634 } else {
3635 discard(_new_val); // since it is not needed in this case
3636 I64x1 inc = pred ? pred->to<int64_t>() : I64x1(1);
3637 count += inc; // increment old count by 1 iff new value is present and `pred` is true
3638 }
3639 }
3640 }
3641 break;
3642 }
3643 }
3644 }
3645
3646 /*----- Compute AVG aggregates after others to ensure that running count is already created. -----*/
3647 for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
3648 auto &info = aggregates[idx];
3649
3650 if (info.fnid == m::Function::FN_AVG) {
3651 M_insist(info.args.size() == 1, "AVG aggregate function expects exactly one argument");
3652 const auto &arg = *info.args[0];
3653 M_insist(info.entry.type->is_double());
3654
3655 auto it = avg_aggregates.find(info.entry.id);
3656 M_insist(it != avg_aggregates.end());
3657 const auto &avg_info = it->second;
3658 M_insist(avg_info.compute_running_avg,
3659 "AVG aggregate may only occur for running average computations");
3660
3661 auto &[avg, is_null] = *M_notnull((
3662 std::get_if<std::pair<Var<Doublex1>, std::optional<Var<Boolx1>>>>(&agg_values[idx])
3663 ));
3664
3665 BLOCK_OPEN(reset_aggs) {
3666 avg = 0.0;
3667 if (is_null)
3668 is_null->set_true();
3669 }
3670
3671 BLOCK_OPEN(update_avg_aggs) {
3672 /* Compute AVG as iterative mean as described in Knuth, The Art of Computer Programming
3673 * Vol 2, section 4.2.2. */
3674 auto running_count_idx = std::distance(
3675 aggregates.cbegin(),
3676 std::find_if(aggregates.cbegin(), aggregates.cend(), [&avg_info](const auto &info){
3677 return info.entry.id == avg_info.running_count;
3678 })
3679 );
3680 M_insist(0 <= running_count_idx and running_count_idx < aggregates.size());
3681 auto &running_count = *M_notnull(std::get_if<Var<I64x1>>(&agg_values[running_count_idx]));
3682
3683 auto _arg = env.compile(arg);
3684 _Doublex1 _new_val = convert<_Doublex1>(_arg);
3685 M_insist(_new_val.can_be_null() == bool(is_null));
3686 if (_new_val.can_be_null()) {
3688 auto _new_val_pred = pred ? Select(*pred, _new_val, _Doublex1::Null()) : _new_val;
3689 auto [new_val, new_val_is_null_] = _new_val_pred.split();
3690 const Var<Boolx1> new_val_is_null(new_val_is_null_); // due to multiple uses
3691
3692 auto delta_absolute = new_val - avg;
3693 auto delta_relative = delta_absolute / running_count.to<double>();
3694
3695 avg += Select(new_val_is_null,
3696 0.0, // ignore NULL
3697 delta_relative); // update old average with new value
3698 *is_null = *is_null and new_val_is_null; // AVG is NULL iff all values are NULL
3699 } else {
3700 auto _new_val_pred = pred ? Select(*pred, _new_val, avg) : _new_val;
3701 auto delta_absolute = _new_val_pred.insist_not_null() - avg;
3702 auto delta_relative = delta_absolute / running_count.to<double>();
3703
3704 avg += delta_relative; // update old average with new value
3705 }
3706 }
3707 }
3708 }
3709
3710 /*----- Compute whether new group starts and update key variables accordingly. -----*/
3711 std::optional<Boolx1> group_differs;
3712 Block update_keys("ordered_grouping.update_grouping_keys", false);
3713 for (std::size_t idx = 0; idx < num_keys; ++idx) {
3714 std::visit(overloaded {
3715 [&]<typename T>(Expr<T> value) -> void {
3716 auto &[key_val, key_is_null] = *M_notnull((
3717 std::get_if<std::pair<Var<PrimitiveExpr<T>>, std::optional<Var<Boolx1>>>>(&key_values[idx])
3718 ));
3719 M_insist(value.can_be_null() == bool(key_is_null));
3720
3721 if (value.can_be_null()) {
3723 auto [val, is_null] = value.clone().split();
3724 auto null_differs = is_null != *key_is_null;
3725 Boolx1 key_differs = null_differs or (not *key_is_null and val != key_val);
3726 if (group_differs)
3727 group_differs.emplace(key_differs or *group_differs);
3728 else
3729 group_differs.emplace(key_differs);
3730
3731 BLOCK_OPEN(update_keys) {
3732 std::tie(key_val, key_is_null) = value.split();
3733 }
3734 } else {
3735 Boolx1 key_differs = key_val != value.clone().insist_not_null();
3736 if (group_differs)
3737 group_differs.emplace(key_differs or *group_differs);
3738 else
3739 group_differs.emplace(key_differs);
3740
3741 BLOCK_OPEN(update_keys) {
3742 key_val = value.insist_not_null();
3743 }
3744 }
3745 },
3746 [&](NChar value) -> void {
3747 auto &key = *M_notnull(std::get_if<Var<Ptr<Charx1>>>(&key_values[idx]));
3748
3749 auto [key_addr, key_is_nullptr] = key.val().split();
3750 auto [addr, is_nullptr] = value.val().clone().split();
3751 auto addr_differs = strncmp(
3752 /* left= */ NChar(addr, value.can_be_null(), value.length(),
3753 value.guarantees_terminating_nul()),
3754 /* right= */ NChar(key_addr, value.can_be_null(), value.length(),
3755 value.guarantees_terminating_nul()),
3756 /* len= */ U32x1(value.length()),
3757 /* op= */ NE
3758 );
3759 auto [addr_differs_value, addr_differs_is_null] = addr_differs.split();
3760 addr_differs_is_null.discard(); // use potentially-null value but it is overruled if it is NULL
3761 auto nullptr_differs = is_nullptr != key_is_nullptr.clone();
3762 Boolx1 key_differs = nullptr_differs or (not key_is_nullptr and addr_differs_value);
3763 if (group_differs)
3764 group_differs.emplace(key_differs or *group_differs);
3765 else
3766 group_differs.emplace(key_differs);
3767
3768 BLOCK_OPEN(update_keys) {
3769 key = value.val();
3770 }
3771 },
3772 [](auto) -> void { M_unreachable("SIMDfication currently not supported"); },
3773 [](std::monostate) -> void { M_unreachable("invalid expression"); },
3774 }, env.compile(M.grouping.group_by()[idx].first.get()));
3775 }
3776 M_insist(bool(group_differs));
3777
3778 /*----- Resume pipeline with computed group iff new one starts and emit code to reset aggregates. ---*/
3779 M_insist(bool(first_iteration));
3780 Boolx1 cond = *first_iteration or *group_differs; // `group_differs` defaulted in first iteration but overruled anyway
3781 IF (pred ? Select(*pred, cond, false) : cond) { // ignore entries for which predication predicate is not fulfilled
3782 IF (not *first_iteration) {
3783 store_locals_to_globals();
3784 emit_group_and_resume_pipeline();
3785 reset_aggs.attach_to_current();
3786 };
3787 update_keys.attach_to_current();
3788 *first_iteration = false;
3789 };
3790
3791 /*----- Emit code to update aggregates. -----*/
3792 update_aggs.attach_to_current();
3793 update_avg_aggs.attach_to_current(); // after others to ensure that running count is incremented before
3794 },
3795 /* teardown= */ teardown_t::Make_Without_Parent([&](){
3796 store_locals_to_globals();
3797
3798 /*----- Destroy created aggregate values and their backups. -----*/
3799 for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
3800 agg_values[idx].~agg_t();
3801 agg_value_backups[idx].~agg_backup_t();
3802 }
3803
3804 M_insist(bool(first_iteration));
3805 first_iteration_backup = *first_iteration;
3806 first_iteration.reset();
3807 })
3808 );
3809
3810 /*----- If input was not empty, emit last group tuple in the current environment and resume the pipeline. -----*/
3811 IF (not first_iteration_backup) {
3812 emit_group_and_resume_pipeline();
3813 };
3814
3815 /*----- Delayed definition of function to emit group and resume pipeline (since result environment is needed). ---*/
3816 auto fn = emit_group_and_resume_pipeline.make_function(); // outside BLOCK_OPEN-macro to register as current function
3817 BLOCK_OPEN(fn.body()) {
3818 auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
3819 auto &env = CodeGenContext::Get().env();
3820
3821 /*----- Emit setup code *before* possibly introducing temporary boolean variables to not overwrite them. -----*/
3822 setup();
3823
3824 /*----- Add computed group tuple to current environment. ----*/
3825 for (auto &e : M.grouping.schema().deduplicate()) {
3826 try {
3827 key_schema.find(e.id);
3828 } catch (invalid_argument&) {
3829 continue; // skip duplicated keys since they must not be used afterwards
3830 }
3831
3832 if (auto it = avg_aggregates.find(e.id);
3833 it != avg_aggregates.end() and not it->second.compute_running_avg)
3834 { // AVG aggregates which is not yet computed, divide computed sum with computed count
3835 auto &avg_info = it->second;
3836 auto sum = results.get(avg_info.sum);
3837 auto count = results.get<_I64x1>(avg_info.running_count).insist_not_null().to<double>();
3838 auto avg = convert<_Doublex1>(sum) / count;
3839 if (avg.can_be_null()) {
3840 _Var<Doublex1> var(avg); // introduce variable s.t. uses only load from it
3841 env.add(e.id, var);
3842 } else {
3843 /* introduce variable w/o NULL bit s.t. uses only load from it */
3844 Var<Doublex1> var(avg.insist_not_null());
3845 env.add(e.id, _Doublex1(var));
3846 }
3847 } else { // part of key or already computed aggregate
3848 std::visit(overloaded {
3849 [&]<typename T>(Expr<T> value) -> void {
3850 if (value.can_be_null()) {
3851 Var<Expr<T>> var(value); // introduce variable s.t. uses only load from it
3852 env.add(e.id, var);
3853 } else {
3854 /* introduce variable w/o NULL bit s.t. uses only load from it */
3855 Var<PrimitiveExpr<T>> var(value.insist_not_null());
3856 env.add(e.id, Expr<T>(var));
3857 }
3858 },
3859 [&](NChar value) -> void {
3860 Var<Ptr<Charx1>> var(value.val()); // introduce variable s.t. uses only load from it
3861 env.add(e.id, NChar(var, value.can_be_null(), value.length(),
3862 value.guarantees_terminating_nul()));
3863 },
3864 [](auto) -> void { M_unreachable("SIMDfication currently not supported"); },
3865 [](std::monostate) -> void { M_unreachable("invalid reference"); },
3866 }, results.get(e.id)); // do not extract to be able to access for not-yet-computed AVG aggregates
3867 }
3868 }
3869
3870 /*----- Resume pipeline. -----*/
3871 pipeline();
3872
3873 /*----- Emit teardown code. -----*/
3874 teardown();
3875 }
3876}
3877
3878
3879/*======================================================================================================================
3880 * Aggregation
3881 *====================================================================================================================*/
3882
3883ConditionSet Aggregation::pre_condition(std::size_t child_idx, const std::tuple<const AggregationOperator*>&)
3884{
3885 M_insist(child_idx == 0);
3886
3887 ConditionSet pre_cond;
3888
3889 return pre_cond;
3890}
3891
3893{
3894 ConditionSet post_cond;
3895
3896 /*----- Aggregation does not introduce predication. -----*/
3897 post_cond.add_condition(Predicated(false));
3898
3899 /*----- Aggregation does implicitly sort the data since only one tuple is produced. -----*/
3900 Sortedness::order_t orders;
3901 for (auto &e : M.aggregation.schema().deduplicate())
3902 orders.add(e.id, Sortedness::O_UNDEF);
3903 post_cond.add_condition(Sortedness(std::move(orders)));
3904
3905 /*----- Aggregation does not introduce SIMD since only one tuple is produced. -----*/
3906 post_cond.add_condition(NoSIMD());
3907
3908 return post_cond;
3909}
3910
3912{
3913 Environment results;
3915 std::vector<std::function<void(void)>> finalize_aggregates;
3916
3917 /*----- Compute information about aggregates, especially about AVG aggregates. -----*/
3918 auto p = compute_aggregate_info(M.aggregation.aggregates(), M.aggregation.schema());
3919 const auto &aggregates = p.first;
3920 const auto &avg_aggregates = p.second;
3921
3922 /*----- Set minimal number of SIMD lanes preferred to get fully utilized SIMD vectors for the aggregate args. ----*/
3923 uint64_t min_size_in_bytes = 16;
3924 for (auto &fn : M.aggregation.aggregates()) {
3925 for (auto &e : fn.get().args) {
3927 [](const m::ast::ErrorExpr&) -> void { M_unreachable("no errors at this stage"); },
3928 [](const m::ast::Designator&) -> void { /* nothing to be done */ },
3929 [](const m::ast::Constant&) -> void { /* nothing to be done */ },
3930 [](const m::ast::QueryExpr&) -> void { /* nothing to be done */ },
3931 [&min_size_in_bytes](const m::ast::FnApplicationExpr &fn) -> void {
3932 M_insist(not fn.get_function().is_aggregate(), "aggregate arguments must not be aggregates");
3933 min_size_in_bytes = std::min(min_size_in_bytes, (fn.type()->size() + 7) / 8);
3934 if (min_size_in_bytes == 1)
3935 throw visit_stop_recursion(); // abort recursion
3936 },
3937 [&min_size_in_bytes](auto &e) -> void { // i.e. for unary and binary expressions
3938 min_size_in_bytes = std::min(min_size_in_bytes, (e.type()->size() + 7) / 8);
3939 if (min_size_in_bytes == 1)
3940 throw visit_stop_recursion(); // abort recursion
3941 }
3943 }
3944 }
3945 CodeGenContext::Get().update_num_simd_lanes_preferred(16 / min_size_in_bytes); // set own preference
3946
3947 /*----- Set minimal number of SIMD lanes preferred to be able to compute running averages. ----*/
3948 if (std::any_of(avg_aggregates.begin(), avg_aggregates.end(), [](auto &i){ return i.second.compute_running_avg; }))
3949 CodeGenContext::Get().update_num_simd_lanes_preferred(4); // set own preference
3950
3951 /*----- Create child function. -----*/
3952 FUNCTION(aggregation_child_pipeline, void(void)) // create function for pipeline
3953 {
3954 auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
3955
3956#ifndef NDEBUG
3957 std::size_t num_simd_lanes;
3958#endif
3959 void *_agg_values;
3960 void *_agg_value_backups;
3961
3962 M.child->execute(
3963 /* setup= */ setup_t::Make_Without_Parent([&]() {
3964 auto execute_setup = [&]<std::size_t L>() {
3965#ifndef NDEBUG
3966 num_simd_lanes = L;
3967#endif
3968
3969 /*----- Initialize aggregates helper structures. -----*/
3970 using agg_t = agg_t_<false, L>;
3971 using agg_backup_t = agg_t_<true, L>;
3972 auto agg_values = new agg_t[aggregates.size()];
3973 auto agg_value_backups = new agg_backup_t[aggregates.size()];
3974
3975 /*----- Store aggregates helper structures for pipeline and teardown callbacks. -----*/
3976 _agg_values = static_cast<void*>(agg_values);
3977 _agg_value_backups = static_cast<void*>(agg_value_backups);
3978
3979 /*----- Initialize aggregates and their backups. -----*/
3980 for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
3981 auto &info = aggregates[idx];
3982
3983 bool is_min = false;
3984 switch (info.fnid) {
3985 default:
3986 M_unreachable("unsupported aggregate function");
3987 case m::Function::FN_MIN:
3988 is_min = true; // set flag and delegate to MAX case
3989 case m::Function::FN_MAX: {
3990 auto min_max = [&]<typename T>() {
3991 auto neutral = is_min ? std::numeric_limits<T>::max()
3992 : std::numeric_limits<T>::lowest();
3993
3994 Var<PrimitiveExpr<T, L>> min_max;
3995 Global<PrimitiveExpr<T, L>> min_max_backup(
3996 neutral // initialize with neutral element +inf or -inf
3997 );
3999 Global<Bool<L>> is_null_backup(true); // MIN/MAX is initially NULL
4000
4001 /*----- Set local aggregate variables to global backups. -----*/
4002 min_max = min_max_backup;
4003 is_null = is_null_backup;
4004
4005 /*----- Add global aggregate to result env. to access it in other function. -----*/
4006 if constexpr (L == 1) { // scalar
4007 PrimitiveExpr<T> value = min_max_backup;
4008 Boolx1 is_null = is_null_backup;
4009 results.add(info.entry.id, Select(is_null, Expr<T>::Null(), value));
4010 } else { // vectorial
4011 /* Create lambda which emits the computation of the final *scalar* aggregate.
4012 * This can then be called in the pipeline function starting at the aggregation
4013 * operator s.t. the emitted variable is a local of the correct function.
4014 * Do not access the global variables inside the lambda using closure by
4015 * reference since they are already destroyed when the lambda will be called.
4016 * Instead, copy their values into the lambda. However, since DSL expressions
4017 * are not const-copy-constructible, we have to allocate them on the heap and
4018 * destroy them manually inside the lambda. */
4019 auto simd_min_max = new PrimitiveExpr<T, L>(min_max_backup.val());
4020 auto simd_is_null = new Bool<L>(is_null_backup.val());
4021 finalize_aggregates.emplace_back([&, is_min, simd_min_max, simd_is_null]() {
4022 PrimitiveExpr<T> value = [&]<std::size_t... Is>(std::index_sequence<Is...>) {
4023 Var<PrimitiveExpr<T>> res(simd_min_max->clone().template extract<0>());
4024 auto update = [&]<std::size_t I>(){
4025 if constexpr (requires (PrimitiveExpr<T> v) { min(v, v); max(v, v); }) {
4026 res = is_min ? min(res, simd_min_max->clone().template extract<I>())
4027 : max(res, simd_min_max->clone().template extract<I>());
4028 } else {
4029 const Var<PrimitiveExpr<T>> extracted(
4030 simd_min_max->clone().template extract<I>()
4031 ); // due to multiple uses
4032 auto cmp = is_min ? extracted < res : extracted > res;
4033#if 1
4034 res = Select(cmp, extracted, res);
4035#else
4036 IF (cmp) {
4037 res = extracted;
4038 };
4039#endif
4040 }
4041 };
4042 (update.template operator()<Is + 1>(), ...);
4043 return res;
4044 }(std::make_index_sequence<L - 1>{});
4045 simd_min_max->discard(); // since it was always cloned
4046 Boolx1 is_null = simd_is_null->all_true();
4047 results.add(info.entry.id, Select(is_null, Expr<T>::Null(), value));
4048 delete simd_min_max; // destroy heap-allocated variable
4049 delete simd_is_null; // destroy heap-allocated variable
4050 });
4051 }
4052
4053 /*----- Move aggregate variables to access them later. ----*/
4054 new (&agg_values[idx]) agg_t(std::make_pair(
4055 std::move(min_max), std::move(is_null))
4056 );
4057 new (&agg_value_backups[idx]) agg_backup_t(std::make_pair(
4058 std::move(min_max_backup), std::move(is_null_backup)
4059 ));
4060 };
4061 auto &n = as<const Numeric>(*info.entry.type);
4062 switch (n.kind) {
4063 case Numeric::N_Int:
4064 case Numeric::N_Decimal:
4065 switch (n.size()) {
4066 default: M_unreachable("invalid size");
4067 case 8: min_max.template operator()<int8_t >(); break;
4068 case 16: min_max.template operator()<int16_t>(); break;
4069 case 32: min_max.template operator()<int32_t>(); break;
4070 case 64: min_max.template operator()<int64_t>(); break;
4071 }
4072 break;
4073 case Numeric::N_Float:
4074 if (n.size() <= 32)
4075 min_max.template operator()<float>();
4076 else
4077 min_max.template operator()<double>();
4078 }
4079 break;
4080 }
4081 case m::Function::FN_AVG:
4082 break; // skip here and handle later
4083 case m::Function::FN_SUM: {
4084 auto sum = [&]<typename T>() {
4086 Global<PrimitiveExpr<T, L>> sum_backup(T(0)); // initialize with neutral element 0
4088 Global<Bool<L>> is_null_backup(true); // SUM is initially NULL
4089
4090 /*----- Set local aggregate variables to global backups. -----*/
4091 sum = sum_backup;
4092 is_null = is_null_backup;
4093
4094 /*----- Add global aggregate to result env. to access it in other function. -----*/
4095 if constexpr (L == 1) { // scalar
4096 PrimitiveExpr<T> value = sum_backup;
4097 Boolx1 is_null = is_null_backup;
4098 results.add(info.entry.id, Select(is_null, Expr<T>::Null(), value));
4099 } else { // vectorial
4100 PrimitiveExpr<T> value = [&]<std::size_t... Is>(std::index_sequence<Is...>) {
4101 return (sum_backup.template extract<Is>() + ...);
4102 }(std::make_index_sequence<L>{});
4103 Boolx1 is_null = is_null_backup.all_true();
4104 results.add(info.entry.id, Select(is_null, Expr<T>::Null(), value));
4105 }
4106
4107 /*----- Move aggregate variables to access them later. ----*/
4108 new (&agg_values[idx]) agg_t(std::make_pair(std::move(sum), std::move(is_null)));
4109 new (&agg_value_backups[idx]) agg_backup_t(std::make_pair(
4110 std::move(sum_backup), std::move(is_null_backup)
4111 ));
4112 };
4113 auto &n = as<const Numeric>(*info.entry.type);
4114 switch (n.kind) {
4115 case Numeric::N_Int:
4116 case Numeric::N_Decimal:
4117 switch (n.size()) {
4118 default: M_unreachable("invalid size");
4119 case 8: sum.template operator()<int8_t >(); break;
4120 case 16: sum.template operator()<int16_t>(); break;
4121 case 32: sum.template operator()<int32_t>(); break;
4122 case 64: sum.template operator()<int64_t>(); break;
4123 }
4124 break;
4125 case Numeric::N_Float:
4126 if (n.size() <= 32)
4127 sum.template operator()<float>();
4128 else
4129 sum.template operator()<double>();
4130 }
4131 break;
4132 }
4133 case m::Function::FN_COUNT: {
4134 Var<I64<L>> count;
4135 Global<I64<L>> count_backup(0); // initialize with neutral element 0
4136 /* no `is_null` variables needed since COUNT will not be NULL */
4137
4138 /*----- Set local aggregate variable to global backup. -----*/
4139 count = count_backup;
4140
4141 /*----- Add global aggregate to result env. to access it in other function. -----*/
4142 if constexpr (L == 1) { // scalar
4143 I64x1 value = count_backup;
4144 results.add(info.entry.id, value);
4145 } else { // vectorial
4146 I64x1 value = [&]<std::size_t... Is>(std::index_sequence<Is...>) {
4147 return (count_backup.template extract<Is>() + ...);
4148 }(std::make_index_sequence<L>{});
4149 results.add(info.entry.id, value);
4150 }
4151
4152 /*----- Move aggregate variables to access them later. ----*/
4153 new (&agg_values[idx]) agg_t(std::move(count));
4154 new (&agg_value_backups[idx]) agg_backup_t(std::move(count_backup));
4155
4156 break;
4157 }
4158 }
4159 }
4160
4161 /*----- Initialize AVG aggregates after others to ensure that running count is initialized before. */
4162 for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
4163 auto &info = aggregates[idx];
4164
4165 if (info.fnid == m::Function::FN_AVG) {
4166 Var<Double<L>> avg;
4167 Global<Double<L>> avg_backup(0.0); // initialize with neutral element 0
4169 Global<Bool<L>> is_null_backup(true); // AVG is initially NULL
4170
4171 /*----- Set local aggregate variables to global backups. -----*/
4172 avg = avg_backup;
4173 is_null = is_null_backup;
4174
4175 /*----- Add global aggregate to result env. to access it in other function. -----*/
4176 if constexpr (L == 1) { // scalar
4177 Doublex1 value = avg_backup;
4178 Boolx1 is_null = is_null_backup;
4179 results.add(info.entry.id, Select(is_null, _Doublex1::Null(), value));
4180 } else { // vectorial
4181 /* Create lambda which emits the computation of the final *scalar* aggregate.
4182 * This can then be called in the pipeline function starting at the aggregation
4183 * operator s.t. the emitted variable is a local of the correct function.
4184 * Do not access the global variables inside the lambda using closure by
4185 * reference since they are already destroyed when the lambda will be called.
4186 * Instead, copy their values into the lambda. However, since DSL expressions
4187 * are not const-copy-constructible, we have to allocate them on the heap and
4188 * destroy them manually inside the lambda. */
4189 auto simd_avg = new Double<L>(avg_backup.val());
4190 auto simd_is_null = new Bool<L>(is_null_backup.val());
4191 auto simd_running_count = new I64<L>([&](){
4192 auto it = avg_aggregates.find(info.entry.id);
4193 M_insist(it != avg_aggregates.end());
4194 const auto &avg_info = it->second;
4195 M_insist(avg_info.compute_running_avg,
4196 "AVG aggregate may only occur for running average computations");
4197
4198 auto running_count_idx = std::distance(
4199 aggregates.cbegin(),
4200 std::find_if(
4201 aggregates.cbegin(), aggregates.cend(), [&avg_info](const auto &info){
4202 return info.entry.id == avg_info.running_count;
4203 })
4204 );
4205 M_insist(0 <= running_count_idx and running_count_idx < aggregates.size());
4206
4207 auto &running_count =
4208 *M_notnull(std::get_if<Global<I64<L>>>(&agg_value_backups[running_count_idx]));
4209 return running_count.val();
4210 }());
4211 finalize_aggregates.emplace_back([&, simd_avg, simd_is_null, simd_running_count]() {
4212 Doublex1 value = [&]<std::size_t... Is>(std::index_sequence<Is...>) {
4213 I64x1 count = (simd_running_count->clone().template extract<Is>() + ...);
4214 const Var<Double<L>> simd_sum([&](){
4215 if constexpr (L != 2) {
4216 return *simd_avg * simd_running_count->template to<double>();
4217 } else {
4218 M_unreachable("conversion from `I64<2>` to `Double<2>` not supported");
4219 return Double<L>(0.0); // this line is never reached; return dummy value
4220 }
4221 }());
4222 return (simd_sum.template extract<Is>() + ...) / count.to<double>();
4223 }(std::make_index_sequence<L>{});
4224 Boolx1 is_null = simd_is_null->all_true();
4225 results.add(info.entry.id, Select(is_null, _Doublex1::Null(), value));
4226 delete simd_avg; // destroy heap-allocated variable
4227 delete simd_is_null; // destroy heap-allocated variable
4228 delete simd_running_count; // destroy heap-allocated variable
4229 });
4230 }
4231
4232 /*----- Move aggregate variables to access them later. ----*/
4233 new (&agg_values[idx]) agg_t(std::make_pair(std::move(avg), std::move(is_null)));
4234 new (&agg_value_backups[idx]) agg_backup_t(std::make_pair(
4235 std::move(avg_backup), std::move(is_null_backup)
4236 ));
4237 }
4238 }
4239 };
4240 switch (CodeGenContext::Get().num_simd_lanes()) {
4241 default: M_unreachable("unsupported number of SIMD lanes");
4242 case 1: execute_setup.operator()<1>(); break;
4243 case 2: execute_setup.operator()<2>(); break;
4244 case 4: execute_setup.operator()<4>(); break;
4245 case 8: execute_setup.operator()<8>(); break;
4246 case 16: execute_setup.operator()<16>(); break;
4247 case 32: execute_setup.operator()<32>(); break;
4248 }
4249 }),
4250 /* pipeline= */ [&](){
4251 auto execute_pipeline = [&]<std::size_t L>(){
4252#ifndef NDEBUG
4254 "number of SIMD lanes in pipeline callback must match the one in setup callback");
4255#endif
4256
4257 /*----- Get aggregates helper structures. -----*/
4258 using agg_t = agg_t_<false, L>;
4259 using agg_backup_t = agg_t_<true, L>;
4260 auto agg_values = static_cast<agg_t*>(_agg_values);
4261 auto agg_value_backups = static_cast<agg_backup_t*>(_agg_value_backups);
4262
4263 auto &env = CodeGenContext::Get().env();
4264
4265 /*----- If predication is used, introduce pred. var. and update it before computing aggregates. --*/
4266 std::optional<Var<Bool<L>>> pred;
4267 if (env.predicated()) {
4268 if constexpr (sql_boolean_type<_Bool<L>>)
4269 pred = env.extract_predicate<_Bool<L>>().is_true_and_not_null();
4270 else
4271 M_unreachable("invalid number of SIMD lanes");
4272 }
4273
4274 /*----- Compute aggregates (except AVG). -----*/
4275 for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
4276 auto &info = aggregates[idx];
4277
4278 bool is_min = false;
4279 switch (info.fnid) {
4280 default:
4281 M_unreachable("unsupported aggregate function");
4282 case m::Function::FN_MIN:
4283 is_min = true; // set flag and delegate to MAX case
4284 case m::Function::FN_MAX: {
4285 M_insist(info.args.size() == 1,
4286 "MIN and MAX aggregate functions expect exactly one argument");
4287 const auto &arg = *info.args[0];
4288 auto min_max = overloaded{
4289 [&]<typename T>() requires sql_type<Expr<T, L>> {
4290 auto &[min_max, is_null] = *M_notnull((
4291 std::get_if<
4292 std::pair<Var<PrimitiveExpr<T, L>>, Var<Bool<L>>>
4293 >(&agg_values[idx])
4294 ));
4295
4296 auto _arg = env.compile(arg);
4297 Expr<T, L> _new_val = convert<Expr<T, L>>(_arg);
4298 if (_new_val.can_be_null()) {
4300 auto _new_val_pred =
4301 pred ? Select(*pred, _new_val, Expr<T, L>::Null()) : _new_val;
4302 auto [new_val_, new_val_is_null_] = _new_val_pred.split();
4303 const Var<Bool<L>> new_val_is_null(new_val_is_null_); // due to multiple uses
4304
4305 if constexpr (requires (PrimitiveExpr<T, L> v) { min(v, v); max(v, v); }) {
4306 min_max = Select(new_val_is_null,
4307 min_max, // ignore NULL
4308 is_min ? min(min_max, new_val_) // update old min with new value
4309 : max(min_max, new_val_)); // update old max with new value
4310 } else {
4311 const Var<PrimitiveExpr<T, L>> new_val(new_val_); // due to multiple uses
4312 auto cmp = is_min ? new_val < min_max : new_val > min_max;
4313#if 1
4314 min_max = Select(new_val_is_null,
4315 min_max, // ignore NULL
4316 Select(cmp,
4317 new_val, // update to new value
4318 min_max)); // do not update
4319#else
4320 IF (not new_val_is_null and cmp) {
4321 min_max = new_val;
4322 };
4323#endif
4324 }
4325 is_null = is_null and new_val_is_null; // MIN/MAX is NULL iff all values are NULL
4326 } else {
4327 auto neutral = is_min ? std::numeric_limits<T>::max()
4328 : std::numeric_limits<T>::lowest();
4329 auto _new_val_pred =
4330 pred ? Select(*pred, _new_val, PrimitiveExpr<T, L>(neutral)) : _new_val;
4331 auto new_val_ = _new_val_pred.insist_not_null();
4332 if constexpr (requires (PrimitiveExpr<T, L> v) { min(v, v); max(v, v); }) {
4333 min_max = is_min ? min(min_max, new_val_) // update old min with new value
4334 : max(min_max, new_val_); // update old max with new value
4335 } else {
4336 const Var<PrimitiveExpr<T, L>> new_val(new_val_); // due to multiple uses
4337 auto cmp = is_min ? new_val < min_max : new_val > min_max;
4338#if 1
4339 min_max = Select(cmp,
4340 new_val, // update to new value
4341 min_max); // do not update
4342#else
4343 IF (cmp) {
4344 min_max = new_val;
4345 };
4346#endif
4347 }
4348 is_null.set_false(); // at least one non-NULL value is consumed
4349 }
4350 },
4351 []<typename>() { M_unreachable("invalid type for given number of SIMD lanes"); }
4352 };
4353 auto &n = as<const Numeric>(*info.entry.type);
4354 switch (n.kind) {
4355 case Numeric::N_Int:
4356 case Numeric::N_Decimal:
4357 switch (n.size()) {
4358 default: M_unreachable("invalid size");
4359 case 8: min_max.template operator()<int8_t >(); break;
4360 case 16: min_max.template operator()<int16_t>(); break;
4361 case 32: min_max.template operator()<int32_t>(); break;
4362 case 64: min_max.template operator()<int64_t>(); break;
4363 }
4364 break;
4365 case Numeric::N_Float:
4366 if (n.size() <= 32)
4367 min_max.template operator()<float>();
4368 else
4369 min_max.template operator()<double>();
4370 }
4371 break;
4372 }
4373 case m::Function::FN_AVG:
4374 break; // skip here and handle later
4375 case m::Function::FN_SUM: {
4376 M_insist(info.args.size() == 1, "SUM aggregate function expects exactly one argument");
4377 const auto &arg = *info.args[0];
4378
4379 auto sum = overloaded{
4380 [&]<typename T>() requires sql_type<Expr<T, L>> {
4381 auto &[sum, is_null] = *M_notnull((
4382 std::get_if<
4383 std::pair<Var<PrimitiveExpr<T, L>>, Var<Bool<L>>>
4384 >(&agg_values[idx])
4385 ));
4386
4387 auto _arg = env.compile(arg);
4388 Expr<T, L> _new_val = convert<Expr<T, L>>(_arg);
4389 if (_new_val.can_be_null()) {
4391 auto _new_val_pred =
4392 pred ? Select(*pred, _new_val, Expr<T, L>::Null()) : _new_val;
4393 auto [new_val, new_val_is_null_] = _new_val_pred.split();
4394 const Var<Bool<L>> new_val_is_null(new_val_is_null_); // due to multiple uses
4395
4396 sum += Select(new_val_is_null,
4397 PrimitiveExpr<T, L>(T(0)), // ignore NULL
4398 new_val); // add new value to old sum
4399 is_null = is_null and new_val_is_null; // SUM is NULL iff all values are NULL
4400 } else {
4401 auto _new_val_pred =
4402 pred ? Select(*pred, _new_val, PrimitiveExpr<T, L>(T(0))) : _new_val;
4403 sum += _new_val_pred.insist_not_null(); // add new value to old sum
4404 is_null.set_false(); // at least one non-NULL value is consumed
4405 }
4406 },
4407 []<typename>() { M_unreachable("invalid type for given number of SIMD lanes"); }
4408 };
4409 auto &n = as<const Numeric>(*info.entry.type);
4410 switch (n.kind) {
4411 case Numeric::N_Int:
4412 case Numeric::N_Decimal:
4413 switch (n.size()) {
4414 default: M_unreachable("invalid size");
4415 case 8: sum.template operator()<int8_t >(); break;
4416 case 16: sum.template operator()<int16_t>(); break;
4417 case 32: sum.template operator()<int32_t>(); break;
4418 case 64: sum.template operator()<int64_t>(); break;
4419 }
4420 break;
4421 case Numeric::N_Float:
4422 if (n.size() <= 32)
4423 sum.template operator()<float>();
4424 else
4425 sum.template operator()<double>();
4426 }
4427 break;
4428 }
4429 case m::Function::FN_COUNT: {
4430 M_insist(info.args.size() <= 1, "COUNT aggregate function expects at most one argument");
4431 M_insist(info.entry.type->is_integral() and info.entry.type->size() == 64);
4432
4433 auto &count = *M_notnull(std::get_if<Var<I64<L>>>(&agg_values[idx]));
4434
4435 if (info.args.empty()) {
4436 count += pred ? pred->template to<int64_t>() : I64<L>(1); // increment old count by 1 iff `pred` is true
4437 } else {
4438 auto _new_val = env.compile(*info.args[0]);
4439 if (can_be_null(_new_val)) {
4441 I64<L> inc = pred ? (not_null<L>(_new_val) and *pred).template to<int64_t>()
4442 : not_null<L>(_new_val).template to<int64_t>();
4443 count += inc; // increment old count by 1 iff new value is present and `pred` is true
4444 } else {
4445 discard(_new_val); // since it is not needed in this case
4446 I64<L> inc = pred ? pred->template to<int64_t>() : I64<L>(1);
4447 count += inc; // increment old count by 1 iff new value is present and `pred` is true
4448 }
4449 }
4450 break;
4451 }
4452 }
4453 }
4454
4455 /*----- Compute AVG aggregates after others to ensure that running count is incremented before. --*/
4456 for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
4457 auto &info = aggregates[idx];
4458
4459 if (info.fnid == m::Function::FN_AVG) {
4460 M_insist(info.args.size() == 1, "AVG aggregate function expects exactly one argument");
4461 const auto &arg = *info.args[0];
4462 M_insist(info.entry.type->is_double());
4463
4464 auto it = avg_aggregates.find(info.entry.id);
4465 M_insist(it != avg_aggregates.end());
4466 const auto &avg_info = it->second;
4467 M_insist(avg_info.compute_running_avg,
4468 "AVG aggregate may only occur for running average computations");
4469
4470 auto &[avg, is_null] = *M_notnull((
4471 std::get_if<std::pair<Var<Double<L>>, Var<Bool<L>>>>(&agg_values[idx])
4472 ));
4473
4474 /* Compute AVG as iterative mean as described in Knuth, The Art of Computer Programming
4475 * Vol 2, section 4.2.2. */
4476 auto running_count_idx = std::distance(
4477 aggregates.cbegin(),
4478 std::find_if(aggregates.cbegin(), aggregates.cend(), [&avg_info](const auto &info){
4479 return info.entry.id == avg_info.running_count;
4480 })
4481 );
4482 M_insist(0 <= running_count_idx and running_count_idx < aggregates.size());
4483 Double<L> running_count = [&](){
4484 auto &running_count =
4485 *M_notnull(std::get_if<Var<I64<L>>>(&agg_values[running_count_idx]));
4486 if constexpr (L != 2) {
4487 return running_count.template to<double>();
4488 } else {
4489 M_unreachable("conversion from `I64<2>` to `Double<2>` not supported");
4490 return Double<L>(0.0); // this line is never reached; return dummy value
4491 }
4492 }();
4493
4494 auto _arg = env.compile(arg);
4495 _Double<L> _new_val = convert<_Double<L>>(_arg);
4496 if (_new_val.can_be_null()) {
4498 auto _new_val_pred = pred ? Select(*pred, _new_val, _Double<L>::Null()) : _new_val;
4499 auto [new_val, new_val_is_null_] = _new_val_pred.split();
4500 const Var<Bool<L>> new_val_is_null(new_val_is_null_); // due to multiple uses
4501
4502 auto delta_absolute = new_val - avg;
4503 auto delta_relative = delta_absolute / running_count;
4504
4505 avg += Select(new_val_is_null,
4506 Double<L>(0.0), // ignore NULL
4507 delta_relative); // update old average with new value
4508 is_null = is_null and new_val_is_null; // AVG is NULL iff all values are NULL
4509 } else {
4510 auto _new_val_pred = pred ? Select(*pred, _new_val, avg) : _new_val;
4511 auto delta_absolute = _new_val_pred.insist_not_null() - avg;
4512 auto delta_relative = delta_absolute / running_count;
4513
4514 avg += delta_relative; // update old average with new value
4515 is_null.set_false(); // at least one non-NULL value is consumed
4516 }
4517 }
4518 }
4519 };
4520 switch (CodeGenContext::Get().num_simd_lanes()) {
4521 default: M_unreachable("unsupported number of SIMD lanes");
4522 case 1: execute_pipeline.operator()<1>(); break;
4523 case 2: execute_pipeline.operator()<2>(); break;
4524 case 4: execute_pipeline.operator()<4>(); break;
4525 case 8: execute_pipeline.operator()<8>(); break;
4526 case 16: execute_pipeline.operator()<16>(); break;
4527 case 32: execute_pipeline.operator()<32>(); break;
4528 }
4529 },
4530 /* teardown= */ teardown_t::Make_Without_Parent([&](){
4531 auto execute_teardown = [&]<std::size_t L>(){
4532#ifndef NDEBUG
4534 "number of SIMD lanes in teardown callback must match the one in setup callback");
4535#endif
4536
4537 /*----- Get aggregates helper structures. -----*/
4538 using agg_t = agg_t_<false, L>;
4539 using agg_backup_t = agg_t_<true, L>;
4540 auto agg_values = static_cast<agg_t*>(_agg_values);
4541 auto agg_value_backups = static_cast<agg_backup_t*>(_agg_value_backups);
4542
4543 /*----- Store local aggregate values to globals to access them in other function. -----*/
4544 for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
4545 auto &info = aggregates[idx];
4546
4547 bool is_min = false;
4548 switch (info.fnid) {
4549 default:
4550 M_unreachable("unsupported aggregate function");
4551 case m::Function::FN_MIN:
4552 is_min = true; // set flag and delegate to MAX case
4553 case m::Function::FN_MAX: {
4554 auto min_max = [&]<typename T>() {
4555 auto &[min_max_backup, is_null_backup] = *M_notnull((
4556 std::get_if<
4557 std::pair<Global<PrimitiveExpr<T, L>>, Global<Bool<L>>>
4558 >(&agg_value_backups[idx])
4559 ));
4560 std::tie(min_max_backup, is_null_backup) = *M_notnull((
4561 std::get_if<std::pair<Var<PrimitiveExpr<T, L>>, Var<Bool<L>>>>(&agg_values[idx])
4562 ));
4563 };
4564 auto &n = as<const Numeric>(*info.entry.type);
4565 switch (n.kind) {
4566 case Numeric::N_Int:
4567 case Numeric::N_Decimal:
4568 switch (n.size()) {
4569 default: M_unreachable("invalid size");
4570 case 8: min_max.template operator()<int8_t >(); break;
4571 case 16: min_max.template operator()<int16_t>(); break;
4572 case 32: min_max.template operator()<int32_t>(); break;
4573 case 64: min_max.template operator()<int64_t>(); break;
4574 }
4575 break;
4576 case Numeric::N_Float:
4577 if (n.size() <= 32)
4578 min_max.template operator()<float>();
4579 else
4580 min_max.template operator()<double>();
4581 }
4582 break;
4583 }
4584 case m::Function::FN_AVG: {
4585 auto &[avg_backup, is_null_backup] = *M_notnull((
4586 std::get_if<std::pair<Global<Double<L>>, Global<Bool<L>>>>(&agg_value_backups[idx])
4587 ));
4588 std::tie(avg_backup, is_null_backup) = *M_notnull((
4589 std::get_if<std::pair<Var<Double<L>>, Var<Bool<L>>>>(&agg_values[idx])
4590 ));
4591
4592 break;
4593 }
4594 case m::Function::FN_SUM: {
4595 M_insist(info.args.size() == 1, "SUM aggregate function expects exactly one argument");
4596 const auto &arg = *info.args[0];
4597
4598 auto sum = [&]<typename T>() {
4599 auto &[sum_backup, is_null_backup] = *M_notnull((
4600 std::get_if<
4601 std::pair<Global<PrimitiveExpr<T, L>>, Global<Bool<L>>>
4602 >(&agg_value_backups[idx])
4603 ));
4604 std::tie(sum_backup, is_null_backup) = *M_notnull((
4605 std::get_if<std::pair<Var<PrimitiveExpr<T, L>>, Var<Bool<L>>>>(&agg_values[idx])
4606 ));
4607 };
4608 auto &n = as<const Numeric>(*info.entry.type);
4609 switch (n.kind) {
4610 case Numeric::N_Int:
4611 case Numeric::N_Decimal:
4612 switch (n.size()) {
4613 default: M_unreachable("invalid size");
4614 case 8: sum.template operator()<int8_t >(); break;
4615 case 16: sum.template operator()<int16_t>(); break;
4616 case 32: sum.template operator()<int32_t>(); break;
4617 case 64: sum.template operator()<int64_t>(); break;
4618 }
4619 break;
4620 case Numeric::N_Float:
4621 if (n.size() <= 32)
4622 sum.template operator()<float>();
4623 else
4624 sum.template operator()<double>();
4625 }
4626 break;
4627 }
4628 case m::Function::FN_COUNT: {
4629 auto &count_backup = *M_notnull(std::get_if<Global<I64<L>>>(&agg_value_backups[idx]));
4630 count_backup = *M_notnull(std::get_if<Var<I64<L>>>(&agg_values[idx]));
4631
4632 break;
4633 }
4634 }
4635 }
4636
4637 /*----- Destroy created aggregates and their backups. -----*/
4638 for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
4639 agg_values[idx].~agg_t();
4640 agg_value_backups[idx].~agg_backup_t();
4641 }
4642
4643 /*----- Free aggregates helper structures. -----*/
4644 delete[] agg_values;
4645 delete[] agg_value_backups;
4646 };
4647 switch (CodeGenContext::Get().num_simd_lanes()) {
4648 default: M_unreachable("unsupported number of SIMD lanes");
4649 case 1: execute_teardown.operator()<1>(); break;
4650 case 2: execute_teardown.operator()<2>(); break;
4651 case 4: execute_teardown.operator()<4>(); break;
4652 case 8: execute_teardown.operator()<8>(); break;
4653 case 16: execute_teardown.operator()<16>(); break;
4654 case 32: execute_teardown.operator()<32>(); break;
4655 }
4656 })
4657 );
4658 }
4659 aggregation_child_pipeline(); // call child function
4660
4661 /*----- Emit setup code *before* possibly introducing temporary boolean variables to not overwrite them. -----*/
4662 setup();
4663
4664 /*----- Emit code to finalize aggregate computations. -----*/
4665 for (auto &fn : finalize_aggregates)
4666 fn();
4667
4668 /*----- Add computed aggregates tuple to current environment. ----*/
4669 auto &env = CodeGenContext::Get().env();
4670 for (auto &e : M.aggregation.schema().deduplicate()) {
4671 if (auto it = avg_aggregates.find(e.id);
4672 it != avg_aggregates.end() and not it->second.compute_running_avg)
4673 { // AVG aggregates which is not yet computed, divide computed sum with computed count
4674 auto &avg_info = it->second;
4675 auto sum = results.get(avg_info.sum);
4676 auto count = results.get<_I64x1>(avg_info.running_count).insist_not_null().to<double>();
4677 auto avg = convert<_Doublex1>(sum) / count;
4678 M_insist(avg.can_be_null());
4679 _Var<Doublex1> var(avg); // introduce variable s.t. uses only load from it
4680 env.add(e.id, var);
4681 } else { // already computed aggregate
4682 std::visit(overloaded {
4683 [&]<typename T>(Expr<T> value) -> void {
4684 if (value.can_be_null()) {
4685 Var<Expr<T>> var(value); // introduce variable s.t. uses only load from it
4686 env.add(e.id, var);
4687 } else {
4688 /* introduce variable w/o NULL bit s.t. uses only load from it */
4689 Var<PrimitiveExpr<T>> var(value.insist_not_null());
4690 env.add(e.id, Expr<T>(var));
4691 }
4692 },
4693 [](auto) -> void { M_unreachable("only scalar and non-string values must occur"); },
4694 [](std::monostate) -> void { M_unreachable("invalid reference"); },
4695 }, results.get(e.id)); // do not extract to be able to access for not-yet-computed AVG aggregates
4696 }
4697 }
4698
4699 /*----- Resume pipeline. -----*/
4700 CodeGenContext::Get().set_num_simd_lanes(1); // since only a single tuple is produced
4701 pipeline();
4702
4703 /*----- Emit teardown code. -----*/
4704 teardown();
4705}
4706
4707
4708/*======================================================================================================================
4709 * Sorting
4710 *====================================================================================================================*/
4711
4712template<bool CmpPredicated>
4713ConditionSet Quicksort<CmpPredicated>::pre_condition(std::size_t child_idx, const std::tuple<const SortingOperator*>&)
4714{
4715 M_insist(child_idx == 0);
4716
4717 ConditionSet pre_cond;
4718
4719 /*----- Sorting does not support SIMD. -----*/
4720 pre_cond.add_condition(NoSIMD());
4721
4722 return pre_cond;
4723}
4724
4725template<bool CmpPredicated>
4727{
4728 ConditionSet post_cond;
4729
4730 /*----- Quicksort does not introduce predication. -----*/
4731 post_cond.add_condition(Predicated(false));
4732
4733 /*----- Quicksort does sort the data. -----*/
4734 Sortedness::order_t orders;
4735 for (auto &o : M.sorting.order_by()) {
4736 Schema::Identifier id(o.first);
4737 if (orders.find(id) == orders.cend())
4738 orders.add(std::move(id), o.second ? Sortedness::O_ASC : Sortedness::O_DESC);
4739 }
4740 post_cond.add_condition(Sortedness(std::move(orders)));
4741
4742 /*----- Sorting does not introduce SIMD. -----*/
4743 post_cond.add_condition(NoSIMD());
4744
4745 return post_cond;
4746}
4747
4748template<bool CmpPredicated>
4750 teardown_t teardown)
4751{
4752 /*----- Create infinite buffer to materialize the current results but resume the pipeline later. -----*/
4753 M_insist(bool(M.materializing_factory), "`wasm::Quicksort` must have a factory for the materialized child");
4754 const auto buffer_schema = M.child->get_matched_root().schema().drop_constants().deduplicate();
4755 const auto sorting_schema = M.sorting.schema().drop_constants().deduplicate();
4756 GlobalBuffer buffer(
4757 buffer_schema, *M.materializing_factory, false, 0, std::move(setup), std::move(pipeline), std::move(teardown)
4758 );
4759
4760 /*----- Create child function. -----*/
4761 FUNCTION(sorting_child_pipeline, void(void)) // create function for pipeline
4762 {
4763 auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
4764
4765 M.child->execute(
4766 /* setup= */ setup_t::Make_Without_Parent([&](){ buffer.setup(); }),
4767 /* pipeline= */ [&](){ buffer.consume(); },
4768 /* teardown= */ teardown_t::Make_Without_Parent([&](){ buffer.teardown(); })
4769 );
4770 }
4771 sorting_child_pipeline(); // call child function
4772
4773 /*----- Invoke quicksort algorithm with buffer to sort. -----*/
4774 quicksort<CmpPredicated>(buffer, M.sorting.order_by());
4775
4776 /*----- Process sorted buffer. -----*/
4777 buffer.resume_pipeline(sorting_schema);
4778}
4779
4781 const std::tuple<const SortingOperator*> &partial_inner_nodes)
4782{
4783 M_insist(child_idx == 0);
4784
4785 ConditionSet pre_cond;
4786
4787 /*----- NoOpSorting, i.e. a noop to match sorting, needs the data already sorted. -----*/
4788 Sortedness::order_t orders;
4789 for (auto &o : std::get<0>(partial_inner_nodes)->order_by()) {
4790 Schema::Identifier id(o.first);
4791 if (orders.find(id) == orders.cend())
4792 orders.add(std::move(id), o.second ? Sortedness::O_ASC : Sortedness::O_DESC);
4793 }
4794 pre_cond.add_condition(Sortedness(std::move(orders)));
4795
4796 return pre_cond;
4797}
4798
4800{
4801 M.child->execute(std::move(setup), std::move(pipeline), std::move(teardown));
4802}
4803
4804
4805/*======================================================================================================================
4806 * Join
4807 *====================================================================================================================*/
4808
4809template<bool Predicated>
4810ConditionSet NestedLoopsJoin<Predicated>::pre_condition(std::size_t, const std::tuple<const JoinOperator*>&)
4811{
4812 ConditionSet pre_cond;
4813
4814 /*----- Nested-loops join does not support SIMD. -----*/
4815 pre_cond.add_condition(NoSIMD());
4816
4817 return pre_cond;
4818}
4819
4820template<bool Predicated>
4823 std::vector<std::reference_wrapper<const ConditionSet>> &&post_cond_children)
4824{
4825 M_insist(post_cond_children.size() >= 2);
4826
4827 ConditionSet post_cond(post_cond_children.back().get()); // preserve conditions of right-most child
4828
4829 if constexpr (Predicated) {
4830 /*----- Predicated nested-loops join introduces predication. -----*/
4831 post_cond.add_or_replace_condition(m::Predicated(true));
4832 }
4833
4834 return post_cond;
4835}
4836
4837template<bool Predicated>
4839{
4840 double cost = 1;
4841 for (auto &child : M.children)
4842 cost *= child->get_matched_root().info().estimated_cardinality;
4843 return cost;
4844}
4845
4846template<bool Predicated>
4848 teardown_t teardown)
4849{
4850 const auto num_left_children = M.children.size() - 1; // all children but right-most one
4851
4852 std::vector<Schema> schemas; // to own adapted schemas
4853 schemas.reserve(num_left_children);
4854 std::vector<GlobalBuffer> buffers;
4855 buffers.reserve(num_left_children);
4856
4857 /*----- Process all but right-most child. -----*/
4858 for (std::size_t i = 0; i < num_left_children; ++i) {
4859 /*----- Create function for each child. -----*/
4860 FUNCTION(nested_loop_join_child_pipeline, void(void))
4861 {
4862 auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
4863
4864 /*----- Create infinite buffer to materialize the current results. -----*/
4865 M_insist(bool(M.materializing_factories_[i]),
4866 "`wasm::NestedLoopsJoin` must have a factory for each materialized child");
4867 const auto &schema = schemas.emplace_back(
4868 M.children[i]->get_matched_root().schema().drop_constants().deduplicate()
4869 );
4870 if (i == 0) {
4871 /*----- Exactly one child (here left-most one) checks join predicate and resumes pipeline. -----*/
4872 buffers.emplace_back(
4873 /* schema= */ schema,
4874 /* factory= */ *M.materializing_factories_[i],
4875 /* load_simdfied= */ false,
4876 /* num_tuples= */ 0, // i.e. infinite
4877 /* setup= */ setup_t::Make_Without_Parent(),
4878 /* pipeline= */ [&, pipeline=std::move(pipeline)](){
4879 if constexpr (Predicated) {
4880 CodeGenContext::Get().env().add_predicate(M.join.predicate());
4881 pipeline();
4882 } else {
4883 M_insist(CodeGenContext::Get().num_simd_lanes() == 1, "invalid number of SIMD lanes");
4884 IF (CodeGenContext::Get().env().compile<_Boolx1>(M.join.predicate()).is_true_and_not_null()) {
4885 pipeline();
4886 };
4887 }
4888 },
4889 /* teardown= */ teardown_t::Make_Without_Parent()
4890 );
4891 } else {
4892 /*----- All but exactly one child (here left-most one) load lastly inserted buffer again. -----*/
4893 /* All buffers are "connected" with each other by setting the pipeline callback as calling the
4894 * `resume_pipeline_inline()` method of the lastly inserted buffer. Therefore, calling
4895 * `resume_pipeline_inline()` on the lastly inserted buffer will load one tuple from it, recursively
4896 * call `resume_pipeline_inline()` on the buffer created before that which again loads one tuple from
4897 * it, and so on until the buffer inserted first (here the one of the left-most child) will load one
4898 * of its tuples and check the join predicate for this one cartesian-product-combination of result
4899 * tuples. */
4900 buffers.emplace_back(
4901 /* schema= */ schema,
4902 /* factory= */ *M.materializing_factories_[i],
4903 /* load_simdfied= */ false,
4904 /* num_tuples= */ 0, // i.e. infinite
4905 /* setup= */ setup_t::Make_Without_Parent(),
4906 /* pipeline= */ [&](){ buffers.back().resume_pipeline_inline(); },
4907 /* teardown= */ teardown_t::Make_Without_Parent()
4908 );
4909 }
4910
4911 /*----- Materialize the current result tuple in pipeline. -----*/
4912 M.children[i]->execute(
4913 /* setup= */ setup_t::Make_Without_Parent([&](){ buffers.back().setup(); }),
4914 /* pipeline= */ [&](){ buffers.back().consume(); },
4915 /* teardown= */ teardown_t::Make_Without_Parent([&](){ buffers.back().teardown(); })
4916 );
4917 }
4918 nested_loop_join_child_pipeline(); // call child function
4919 }
4920
4921 /*----- Process right-most child. -----*/
4922 M.children.back()->execute(
4923 /* setup= */ std::move(setup),
4924 /* pipeline= */ [&](){ buffers.back().resume_pipeline_inline(); },
4925 /* teardown= */ std::move(teardown)
4926 );
4927}
4928
4929template<bool UniqueBuild, bool Predicated>
4931 std::size_t,
4932 const std::tuple<const JoinOperator*, const Wildcard*, const Wildcard*> &partial_inner_nodes)
4933{
4934 ConditionSet pre_cond;
4935
4936 /*----- Simple hash join can only be used for binary joins on equi-predicates. -----*/
4937 auto &join = *std::get<0>(partial_inner_nodes);
4938 if (not join.predicate().is_equi())
4940
4941 if constexpr (UniqueBuild) {
4942 /*----- Decompose each clause of the join predicate of the form `A.x = B.y` into parts `A.x` and `B.y`. -----*/
4943 auto &build = *std::get<1>(partial_inner_nodes);
4944 for (auto &clause : join.predicate()) {
4945 M_insist(clause.size() == 1, "invalid equi-predicate");
4946 auto &literal = clause[0];
4947 auto &binary = as<const BinaryExpr>(literal.expr());
4948 M_insist((not literal.negative() and binary.tok == TK_EQUAL) or
4949 (literal.negative() and binary.tok == TK_BANG_EQUAL), "invalid equi-predicate");
4950 M_insist(is<const Designator>(binary.lhs), "invalid equi-predicate");
4951 M_insist(is<const Designator>(binary.rhs), "invalid equi-predicate");
4952 Schema::Identifier id_first(*binary.lhs), id_second(*binary.rhs);
4953 const auto &entry_build = build.schema().has(id_first) ? build.schema()[id_first].second
4954 : build.schema()[id_second].second;
4955
4956 /*----- Unique simple hash join can only be used on unique build key. -----*/
4957 if (not entry_build.unique())
4959 }
4960 }
4961
4962 /*----- Simple hash join does not support SIMD. -----*/
4963 pre_cond.add_condition(NoSIMD());
4964
4965 return pre_cond;
4966}
4967
4968template<bool UniqueBuild, bool Predicated>
4970 const Match<SimpleHashJoin>&,
4971 std::vector<std::reference_wrapper<const ConditionSet>> &&post_cond_children)
4972{
4973 M_insist(post_cond_children.size() == 2);
4974
4975 ConditionSet post_cond(post_cond_children[1].get()); // preserve conditions of right child
4976
4977 if constexpr (Predicated) {
4978 /*----- Predicated simple hash join introduces predication. -----*/
4979 post_cond.add_or_replace_condition(m::Predicated(true));
4980 } else {
4981 /*----- Branching simple hash join does not introduce predication (it is already handled by the hash table). -*/
4982 post_cond.add_or_replace_condition(m::Predicated(false));
4983 }
4984
4985 return post_cond;
4986}
4987
4988template<bool UniqueBuild, bool Predicated>
4990{
4992 return (M.build.id() == M.children[0]->get_matched_root().id() ? 1.0 : 2.0) + (UniqueBuild ? 0.0 : 0.1);
4994 return M.build.id() == M.children[1]->get_matched_root().id() ? 1.0 : 2.0 + (UniqueBuild ? 0.0 : 0.1);
4995 else
4996 return 1.5 * M.build.info().estimated_cardinality +
4997 (UniqueBuild ? 1.0 : 1.1) * M.probe.info().estimated_cardinality;
4998}
4999
5000template<bool UniqueBuild, bool Predicated>
5002 pipeline_t pipeline, teardown_t teardown)
5003{
5004 // TODO: determine setup
5005 const uint64_t PAYLOAD_SIZE_THRESHOLD_IN_BITS =
5006 M.use_in_place_values ? std::numeric_limits<uint64_t>::max() : 0;
5007
5008 M_insist(((M.join.schema() | M.join.predicate().get_required()) & M.build.schema()) == M.build.schema());
5009 M_insist(M.build.schema().drop_constants() == M.build.schema());
5010 const auto ht_schema = M.build.schema().deduplicate();
5011
5012 /*----- Decompose each clause of the join predicate of the form `A.x = B.y` into parts `A.x` and `B.y`. -----*/
5013 const auto [build_keys, probe_keys] = decompose_equi_predicate(M.join.predicate(), ht_schema);
5014
5015 /*----- Compute payload IDs and its total size in bits (ignoring padding). -----*/
5016 std::vector<Schema::Identifier> payload_ids;
5017 uint64_t payload_size_in_bits = 0;
5018 for (auto &e : ht_schema) {
5019 if (not contains(build_keys, e.id)) {
5020 payload_ids.push_back(e.id);
5021 payload_size_in_bits += e.type->size();
5022 }
5023 }
5024
5025 /*----- Compute initial capacity of hash table. -----*/
5026 uint32_t initial_capacity = compute_initial_ht_capacity(M.build, M.load_factor);
5027
5028 /*----- Create hash table for build child. -----*/
5029 std::unique_ptr<HashTable> ht;
5030 std::vector<HashTable::index_t> build_key_indices;
5031 for (auto &build_key : build_keys)
5032 build_key_indices.push_back(ht_schema[build_key].first);
5033 if (M.use_open_addressing_hashing) {
5034 if (payload_size_in_bits < PAYLOAD_SIZE_THRESHOLD_IN_BITS)
5035 ht = std::make_unique<GlobalOpenAddressingInPlaceHashTable>(ht_schema, std::move(build_key_indices),
5036 initial_capacity);
5037 else
5038 ht = std::make_unique<GlobalOpenAddressingOutOfPlaceHashTable>(ht_schema, std::move(build_key_indices),
5039 initial_capacity);
5040 if (M.use_quadratic_probing)
5041 as<OpenAddressingHashTableBase>(*ht).set_probing_strategy<QuadraticProbing>();
5042 else
5043 as<OpenAddressingHashTableBase>(*ht).set_probing_strategy<LinearProbing>();
5044 } else {
5045 ht = std::make_unique<GlobalChainedHashTable>(ht_schema, std::move(build_key_indices), initial_capacity);
5046 }
5047
5048 /*----- Create function for build child. -----*/
5049 FUNCTION(simple_hash_join_child_pipeline, void(void)) // create function for pipeline
5050 {
5051 auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
5052
5053 M.children[0]->execute(
5054 /* setup= */ setup_t::Make_Without_Parent([&](){
5055 ht->setup();
5056 ht->set_high_watermark(M.load_factor);
5057 }),
5058 /* pipeline= */ [&](){
5059 auto &env = CodeGenContext::Get().env();
5060
5061 std::optional<Boolx1> build_key_not_null;
5062 for (auto &build_key : build_keys) {
5063 auto val = env.get(build_key);
5064 if (build_key_not_null)
5065 build_key_not_null.emplace(*build_key_not_null and not_null(val));
5066 else
5067 build_key_not_null.emplace(not_null(val));
5068 }
5069 M_insist(bool(build_key_not_null));
5070 IF (*build_key_not_null) { // TODO: predicated version
5071 /*----- Insert key. -----*/
5072 std::vector<SQL_t> key;
5073 for (auto &build_key : build_keys)
5074 key.emplace_back(env.get(build_key));
5075 auto entry = ht->emplace(std::move(key));
5076
5077 /*----- Insert payload. -----*/
5078 for (auto &id : payload_ids) {
5079 std::visit(overloaded {
5080 [&]<sql_type T>(HashTable::reference_t<T> &&r) -> void { r = env.extract<T>(id); },
5081 [](std::monostate) -> void { M_unreachable("invalid reference"); },
5082 }, entry.extract(id));
5083 }
5084 };
5085 },
5086 /* teardown= */ teardown_t::Make_Without_Parent([&](){ ht->teardown(); })
5087 );
5088 }
5089 simple_hash_join_child_pipeline(); // call child function
5090
5091 M.children[1]->execute(
5092 /* setup= */ setup_t(std::move(setup), [&](){ ht->setup(); }),
5093 /* pipeline= */ [&, pipeline=std::move(pipeline)](){
5094 auto &env = CodeGenContext::Get().env();
5095
5096 auto emit_tuple_and_resume_pipeline = [&, pipeline=std::move(pipeline)](HashTable::const_entry_t entry){
5097 /*----- Add found entry from hash table, i.e. from build child, to current environment. -----*/
5098 for (auto &e : ht_schema) {
5099 if (not entry.has(e.id)) { // entry may not contain build key in case `ht->find()` was used
5100 M_insist(contains(build_keys, e.id));
5101 M_insist(env.has(e.id), "build key must already be contained in the current environment");
5102 continue;
5103 }
5104
5105 std::visit(overloaded {
5106 [&]<typename T>(HashTable::const_reference_t<Expr<T>> &&r) -> void {
5107 Expr<T> value = r;
5108 if (value.can_be_null()) {
5109 Var<Expr<T>> var(value); // introduce variable s.t. uses only load from it
5110 env.add(e.id, var);
5111 } else {
5112 /* introduce variable w/o NULL bit s.t. uses only load from it */
5113 Var<PrimitiveExpr<T>> var(value.insist_not_null());
5114 env.add(e.id, Expr<T>(var));
5115 }
5116 },
5117 [&](HashTable::const_reference_t<NChar> &&r) -> void {
5118 NChar value(r);
5119 Var<Ptr<Charx1>> var(value.val()); // introduce variable s.t. uses only load from it
5120 env.add(e.id, NChar(var, value.can_be_null(), value.length(),
5121 value.guarantees_terminating_nul()));
5122 },
5123 [](std::monostate) -> void { M_unreachable("invalid reference"); },
5124 }, entry.extract(e.id));
5125 }
5126
5127 /*----- Resume pipeline. -----*/
5128 pipeline();
5129 };
5130
5131 /* TODO: may check for NULL on probe keys as well, branching + predicated version */
5132 /*----- Probe with probe key. -----*/
5133 std::vector<SQL_t> key;
5134 for (auto &probe_key : probe_keys)
5135 key.emplace_back(env.get(probe_key));
5136 if constexpr (UniqueBuild) {
5137 /*----- Add build key to current environment since `ht->find()` will only return the payload values. -----*/
5138 for (auto build_it = build_keys.cbegin(), probe_it = probe_keys.cbegin(); build_it != build_keys.cend();
5139 ++build_it, ++probe_it)
5140 {
5141 M_insist(probe_it != probe_keys.cend());
5142 if (not env.has(*build_it)) // skip duplicated build keys and only add first occurrence
5143 env.add(*build_it, env.get(*probe_it)); // since build and probe keys match for join partners
5144 }
5145
5146 /*----- Try to find the *single* possible join partner. -----*/
5147 auto p = ht->find(std::move(key));
5148 auto &entry = p.first;
5149 auto &found = p.second;
5150 if constexpr (Predicated) {
5151 env.add_predicate(found);
5152 emit_tuple_and_resume_pipeline(std::move(entry));
5153 } else {
5154 IF (found) {
5155 emit_tuple_and_resume_pipeline(std::move(entry));
5156 };
5157 }
5158 } else {
5159 /*----- Search for *all* join partners. -----*/
5160 ht->for_each_in_equal_range(std::move(key), std::move(emit_tuple_and_resume_pipeline), Predicated);
5161 }
5162 },
5163 /* teardown= */ teardown_t(std::move(teardown), [&](){ ht->teardown(); })
5164 );
5165}
5166
5167template<bool SortLeft, bool SortRight, bool Predicated, bool CmpPredicated>
5169 std::size_t child_idx,
5170 const std::tuple<const JoinOperator*, const Wildcard*, const Wildcard*> &partial_inner_nodes)
5171{
5172 ConditionSet pre_cond;
5173
5174 /*----- Sort merge join can only be used for binary joins on conjunctions of equi-predicates. -----*/
5175 auto &join = *std::get<0>(partial_inner_nodes);
5176 if (not join.predicate().is_equi())
5178
5179 /*----- Decompose each clause of the join predicate of the form `A.x = B.y` into parts `A.x` and `B.y`. -----*/
5180 auto parent = std::get<1>(partial_inner_nodes);
5181 auto child = std::get<2>(partial_inner_nodes);
5182 M_insist(parent);
5183 M_insist(child_idx != 1 or child);
5184 std::vector<Schema::Identifier> keys_parent, keys_child;
5185 for (auto &clause : join.predicate()) {
5186 M_insist(clause.size() == 1, "invalid equi-predicate");
5187 auto &literal = clause[0];
5188 auto &binary = as<const BinaryExpr>(literal.expr());
5189 M_insist((not literal.negative() and binary.tok == TK_EQUAL) or
5190 (literal.negative() and binary.tok == TK_BANG_EQUAL), "invalid equi-predicate");
5191 M_insist(is<const Designator>(binary.lhs), "invalid equi-predicate");
5192 M_insist(is<const Designator>(binary.rhs), "invalid equi-predicate");
5193 Schema::Identifier id_first(*binary.lhs), id_second(*binary.rhs);
5195 const auto &[entry_parent, entry_child] = parent->schema().has(id_first)
5196 ? std::make_pair(parent->schema()[id_first].second, child_idx == 1 ? child->schema()[id_second].second : std::move(dummy))
5197 : std::make_pair(parent->schema()[id_second].second, child_idx == 1 ? child->schema()[id_first].second : std::move(dummy));
5198 keys_parent.push_back(entry_parent.id);
5199 keys_child.push_back(entry_child.id);
5200
5201 /*----- Sort merge join can only be used on unique parent key. -----*/
5202 if (not entry_parent.unique())
5204 }
5205 M_insist(keys_parent.size() == keys_child.size(), "number of found IDs differ");
5206 M_insist(not keys_parent.empty(), "must find at least one ID");
5207
5208 if constexpr (not SortLeft or not SortRight) {
5209 /*----- Sort merge join without sorting needs its data sorted on the respective key. -----*/
5210 Sortedness::order_t orders;
5211 M_insist(child_idx < 2);
5212 if (not SortLeft and child_idx == 0) {
5213 for (auto &key_parent : keys_parent) {
5214 if (orders.find(key_parent) == orders.cend())
5215 orders.add(key_parent, Sortedness::O_ASC); // TODO: support different order
5216 }
5217 } else if (not SortRight and child_idx == 1) {
5218 for (auto &key_child : keys_child) {
5219 if (orders.find(key_child) == orders.cend())
5220 orders.add(key_child, Sortedness::O_ASC); // TODO: support different order
5221 }
5222 }
5223 pre_cond.add_condition(Sortedness(std::move(orders)));
5224 }
5225
5226 /*----- Sort merge join does not support SIMD. -----*/
5227 pre_cond.add_condition(NoSIMD());
5228
5229 return pre_cond;
5230}
5231
5232template<bool SortLeft, bool SortRight, bool Predicated, bool CmpPredicated>
5234 const Match<SortMergeJoin> &M,
5235 std::vector<std::reference_wrapper<const ConditionSet>> &&post_cond_children)
5236{
5237 M_insist(post_cond_children.size() == 2);
5238
5239 ConditionSet post_cond;
5240
5241 if constexpr (Predicated) {
5242 /*----- Predicated sort merge join introduces predication. -----*/
5243 post_cond.add_or_replace_condition(m::Predicated(true));
5244 }
5245
5246 /*----- Sort merge join does not introduce SIMD. -----*/
5247 post_cond.add_condition(NoSIMD());
5248
5249 Sortedness::order_t orders;
5250 if constexpr (not SortLeft) {
5251 Sortedness sorting_left(post_cond_children[0].get().get_condition<Sortedness>());
5252 orders.merge(sorting_left.orders()); // preserve sortedness of left child (including order)
5253 }
5254 if constexpr (not SortRight) {
5255 Sortedness sorting_right(post_cond_children[1].get().get_condition<Sortedness>());
5256 orders.merge(sorting_right.orders()); // preserve sortedness of right child (including order)
5257 }
5258 if constexpr (SortLeft or SortRight) {
5259 /*----- Decompose each clause of the join predicate of the form `A.x = B.y` into parts `A.x` and `B.y`. -----*/
5260 auto [keys_parent, keys_child] = decompose_equi_predicate(M.join.predicate(), M.parent.schema());
5261
5262 /*----- Sort merge join does sort the data on the respective key. -----*/
5263 if constexpr (SortLeft) {
5264 for (auto &key_parent : keys_parent) {
5265 if (orders.find(key_parent) == orders.cend())
5266 orders.add(key_parent, Sortedness::O_ASC); // add sortedness for left child
5267 }
5268 }
5269 if constexpr (SortRight) {
5270 for (auto &key_child : keys_child) {
5271 if (orders.find(key_child) == orders.cend())
5272 orders.add(key_child, Sortedness::O_ASC); // add sortedness for right child
5273 }
5274 }
5275 }
5276 post_cond.add_condition(Sortedness(std::move(orders)));
5277
5278 return post_cond;
5279}
5280
5281template<bool SortLeft, bool SortRight, bool Predicated, bool CmpPredicated>
5283{
5284 const double card_left = M.parent.info().estimated_cardinality;
5285 const double card_right = M.child.info().estimated_cardinality;
5286
5287 double cost = card_left + card_right; // cost for merge
5288 if constexpr (SortLeft)
5289 cost += std::log2(card_left) * card_left; // cost for sort left
5290 if constexpr (SortRight)
5291 cost += std::log2(card_right) * card_right; // cost for sort right
5292
5293 return cost;
5294}
5295
5296template<bool SortLeft, bool SortRight, bool Predicated, bool CmpPredicated>
5298 const Match<SortMergeJoin> &M,
5299 setup_t setup,
5300 pipeline_t pipeline,
5301 teardown_t teardown)
5302{
5303 auto &env = CodeGenContext::Get().env();
5304 const bool needs_buffer_parent = not is<const ScanOperator>(M.parent) or SortLeft;
5305 const bool needs_buffer_child = not is<const ScanOperator>(M.child) or SortRight;
5306
5307 /*----- Create infinite buffers to materialize the current results (if necessary). -----*/
5308 M_insist(bool(M.left_materializing_factory),
5309 "`wasm::SortMergeJoin` must have a factory for the materialized left child");
5310 M_insist(bool(M.right_materializing_factory),
5311 "`wasm::SortMergeJoin` must have a factory for the materialized right child");
5312 const auto schema_parent = M.parent.schema().drop_constants().deduplicate();
5313 const auto schema_child = M.child.schema().drop_constants().deduplicate();
5314 std::optional<GlobalBuffer> buffer_parent, buffer_child;
5315 if (needs_buffer_parent)
5316 buffer_parent.emplace(schema_parent, *M.left_materializing_factory);
5317 if (needs_buffer_child)
5318 buffer_child.emplace(schema_child, *M.right_materializing_factory);
5319
5320 /*----- Create child functions. -----*/
5321 if (needs_buffer_parent) {
5322 FUNCTION(sort_merge_join_parent_pipeline, void(void)) // create function for parent pipeline
5323 {
5324 auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
5325 M.children[0]->execute(
5326 /* setup= */ setup_t::Make_Without_Parent([&](){ buffer_parent->setup(); }),
5327 /* pipeline= */ [&](){ buffer_parent->consume(); },
5328 /* teardown= */ teardown_t::Make_Without_Parent([&](){ buffer_parent->teardown(); })
5329 );
5330 }
5331 sort_merge_join_parent_pipeline(); // call parent function
5332 }
5333 if (needs_buffer_child) {
5334 FUNCTION(sort_merge_join_child_pipeline, void(void)) // create function for child pipeline
5335 {
5336 auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
5337 M.children[1]->execute(
5338 /* setup= */ setup_t::Make_Without_Parent([&](){ buffer_child->setup(); }),
5339 /* pipeline= */ [&](){ buffer_child->consume(); },
5340 /* teardown= */ teardown_t::Make_Without_Parent([&](){ buffer_child->teardown(); })
5341 );
5342 }
5343 sort_merge_join_child_pipeline(); // call child function
5344 }
5345
5346 /*----- Decompose each clause of the join predicate of the form `A.x = B.y` into parts `A.x` and `B.y`. -----*/
5347 std::vector<SortingOperator::order_type> order_parent, order_child;
5348 for (auto &clause : M.join.predicate()) {
5349 M_insist(clause.size() == 1, "invalid equi-predicate");
5350 auto &literal = clause[0];
5351 auto &binary = as<const BinaryExpr>(literal.expr());
5352 M_insist((not literal.negative() and binary.tok == TK_EQUAL) or
5353 (literal.negative() and binary.tok == TK_BANG_EQUAL), "invalid equi-predicate");
5354 M_insist(is<const Designator>(binary.lhs), "invalid equi-predicate");
5355 M_insist(is<const Designator>(binary.rhs), "invalid equi-predicate");
5356 auto [expr_parent, expr_child] = M.parent.schema().has(Schema::Identifier(*binary.lhs)) ?
5357 std::make_pair(binary.lhs.get(), binary.rhs.get()) : std::make_pair(binary.rhs.get(), binary.lhs.get());
5358 order_parent.emplace_back(*expr_parent, true); // ascending order
5359 order_child.emplace_back(*expr_child, true); // ascending order
5360 }
5361 M_insist(order_parent.size() == order_child.size(), "number of found IDs differ");
5362 M_insist(not order_parent.empty(), "must find at least one ID");
5363
5364 /*----- If necessary, invoke sorting algorithm with buffer to sort. -----*/
5365 if constexpr (SortLeft)
5366 quicksort<CmpPredicated>(*buffer_parent, order_parent);
5367 if constexpr (SortRight)
5368 quicksort<CmpPredicated>(*buffer_child, order_child);
5369
5370 /*----- Create predicate to check if child co-group is smaller or equal than the one of the parent relation. -----*/
5371 auto child_smaller_equal = [&]() -> Boolx1 {
5372 std::optional<Boolx1> child_smaller_equal_;
5373 for (std::size_t i = 0; i < order_child.size(); ++i) {
5374 auto &des_parent = as<const Designator>(order_parent[i].first);
5375 auto &des_child = as<const Designator>(order_child[i].first);
5376 Token leq = Token::CreateArtificial(TK_LESS_EQUAL);
5377 auto cpy_parent = std::make_unique<Designator>(des_parent.tok, des_parent.table_name, des_parent.attr_name,
5378 des_parent.type(), des_parent.target());
5379 auto cpy_child = std::make_unique<Designator>(des_child.tok, des_child.table_name, des_child.attr_name,
5380 des_child.type(), des_child.target());
5381 BinaryExpr expr(std::move(leq), std::move(cpy_child), std::move(cpy_parent));
5382
5383 auto child = env.get(Schema::Identifier(des_child));
5384 Boolx1 cmp = env.compile<_Boolx1>(expr).is_true_and_not_null();
5385 if (child_smaller_equal_)
5386 child_smaller_equal_.emplace(*child_smaller_equal_ and (is_null(child) or cmp));
5387 else
5388 child_smaller_equal_.emplace(is_null(child) or cmp);
5389 }
5390 M_insist(bool(child_smaller_equal_));
5391 return *child_smaller_equal_;
5392 };
5393
5394 /*----- Compile data layouts to generate sequential loads from buffers. -----*/
5395 static Schema empty_schema;
5396 Var<U32x1> tuple_id_parent, tuple_id_child; // default initialized to 0
5397 auto [inits_parent, loads_parent, _jumps_parent] = [&](){
5398 if (needs_buffer_parent) {
5399 return compile_load_sequential(buffer_parent->schema(), empty_schema, buffer_parent->base_address(),
5400 buffer_parent->layout(), 1, buffer_parent->schema(), tuple_id_parent);
5401 } else {
5402 auto &scan = as<const ScanOperator>(M.parent);
5403 return compile_load_sequential(schema_parent, empty_schema, get_base_address(scan.store().table().name()),
5404 scan.store().table().layout(), 1, scan.store().table().schema(scan.alias()),
5405 tuple_id_parent);
5406 }
5407 }();
5408 auto [inits_child, loads_child, _jumps_child] = [&](){
5409 if (needs_buffer_child) {
5410 return compile_load_sequential(buffer_child->schema(), empty_schema, buffer_child->base_address(),
5411 buffer_child->layout(), 1, buffer_child->schema(), tuple_id_child);
5412 } else {
5413 auto &scan = as<const ScanOperator>(M.child);
5414 return compile_load_sequential(schema_child, empty_schema, get_base_address(scan.store().table().name()),
5415 scan.store().table().layout(), 1, scan.store().table().schema(scan.alias()),
5416 tuple_id_child);
5417 }
5418 }();
5419 /* since structured bindings cannot be used in lambda capture */
5420 Block jumps_parent(std::move(_jumps_parent)), jumps_child(std::move(_jumps_child));
5421
5422 /*----- Process both buffers together. -----*/
5423 setup();
5424 inits_parent.attach_to_current();
5425 inits_child.attach_to_current();
5426 U32x1 size_parent = needs_buffer_parent ? buffer_parent->size()
5427 : get_num_rows(as<const ScanOperator>(M.parent).store().table().name());
5428 U32x1 size_child = needs_buffer_child ? buffer_child->size()
5429 : get_num_rows(as<const ScanOperator>(M.child).store().table().name());
5430 WHILE (tuple_id_parent < size_parent and tuple_id_child < size_child) { // neither end reached
5431 loads_parent.attach_to_current();
5432 loads_child.attach_to_current();
5433 if constexpr (Predicated) {
5434 env.add_predicate(M.join.predicate());
5435 pipeline();
5436 } else {
5437 M_insist(CodeGenContext::Get().num_simd_lanes() == 1, "invalid number of SIMD lanes");
5438 IF (env.compile<_Boolx1>(M.join.predicate()).is_true_and_not_null()) { // predicate fulfilled
5439 pipeline();
5440 };
5441 }
5442 IF (child_smaller_equal()) {
5443 jumps_child.attach_to_current();
5444 } ELSE {
5445 jumps_parent.attach_to_current();
5446 };
5447 }
5448 teardown();
5449}
5450
5451
5452/*======================================================================================================================
5453 * Limit
5454 *====================================================================================================================*/
5455
5456ConditionSet Limit::pre_condition(std::size_t child_idx, const std::tuple<const LimitOperator*>&)
5457{
5458 M_insist(child_idx == 0);
5459
5460 ConditionSet pre_cond;
5461
5462 /*----- Limit does not support SIMD. -----*/
5463 pre_cond.add_condition(NoSIMD());
5464
5465 return pre_cond;
5466}
5467
5468void Limit::execute(const Match<Limit> &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
5469{
5470 std::optional<Block> teardown_block;
5471 std::optional<BlockUser> use_teardown;
5472
5473 std::optional<Var<U32x1>> counter;
5474 /* default initialized to 0 */
5475 Global<U32x1> counter_backup;
5476
5477 M.child->execute(
5478 /* setup= */ setup_t(std::move(setup), [&](){
5479 counter.emplace(counter_backup);
5480 teardown_block.emplace("limit.teardown", true); // create block
5481 use_teardown.emplace(*teardown_block); // set block active s.t. it contains all following pipeline code
5482 }),
5483 /* pipeline= */ [&, pipeline=std::move(pipeline)](){
5484 M_insist(bool(teardown_block));
5485 M_insist(bool(counter));
5486 const uint32_t limit = M.limit.offset() + M.limit.limit();
5487
5488 /*----- Abort pipeline, i.e. go to teardown code, if limit is exceeded. -----*/
5489 IF (*counter >= limit) {
5490 GOTO(*teardown_block);
5491 };
5492
5493 /*----- Emit result if in bounds. -----*/
5494 if (M.limit.offset()) {
5495 IF (*counter >= uint32_t(M.limit.offset())) {
5496 Wasm_insist(*counter < limit, "counter must not exceed limit");
5497 pipeline();
5498 };
5499 } else {
5500 Wasm_insist(*counter < limit, "counter must not exceed limit");
5501 pipeline();
5502 }
5503
5504 /*----- Update counter. -----*/
5505 *counter += 1U;
5506 },
5507 /* teardown= */ teardown_t::Make_Without_Parent([&, teardown=std::move(teardown)](){
5508 M_insist(bool(teardown_block));
5509 M_insist(bool(use_teardown));
5510 use_teardown.reset(); // deactivate block
5511 teardown_block.reset(); // emit block containing pipeline code into parent -> GOTO jumps here
5512 teardown(); // *before* own teardown code to *not* jump over it in case of another limit operator
5513 M_insist(bool(counter));
5514 counter_backup = *counter;
5515 counter.reset();
5516 })
5517 );
5518}
5519
5520
5521/*======================================================================================================================
5522 * Grouping combined with Join
5523 *====================================================================================================================*/
5524
5526 std::size_t child_idx,
5527 const std::tuple<const GroupingOperator*, const JoinOperator*, const Wildcard*, const Wildcard*>
5528 &partial_inner_nodes)
5529{
5530 ConditionSet pre_cond;
5531
5532 /*----- Hash-based group-join can only be used if aggregates only depend on either build or probe relation. -----*/
5533 auto &grouping = *std::get<0>(partial_inner_nodes);
5534 for (auto &fn_expr : grouping.aggregates()) {
5535 M_insist(fn_expr.get().args.size() <= 1);
5536 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
5538 }
5539
5540 /*----- Hash-based group-join can only be used for binary joins on equi-predicates. -----*/
5541 auto &join = *std::get<1>(partial_inner_nodes);
5542 if (not join.predicate().is_equi())
5544
5545 M_insist(child_idx < 2);
5546 if (child_idx == 0) {
5547 /*----- Decompose each clause of the join predicate of the form `A.x = B.y` into parts `A.x` and `B.y`. -----*/
5548 auto &build = *std::get<2>(partial_inner_nodes);
5549 const auto build_keys = decompose_equi_predicate(join.predicate(), build.schema()).first;
5550
5551 /*----- Hash-based group-join can only be used if grouping and join (i.e. build) key match (ignoring order). -*/
5552 const auto num_grouping_keys = grouping.group_by().size();
5553 if (num_grouping_keys != build_keys.size()) // XXX: duplicated IDs are still a match but rejected here
5555 for (std::size_t i = 0; i < num_grouping_keys; ++i) {
5556 Schema::Identifier grouping_key(grouping.group_by()[i].first.get());
5557 if (not contains(build_keys, grouping_key))
5559 }
5560 }
5561
5562 /*----- Hash-based group-join does not support SIMD. -----*/
5563 pre_cond.add_condition(NoSIMD());
5564
5565 return pre_cond;
5566}
5567
5569{
5570 return 1.5 * M.build.info().estimated_cardinality + 1.0 * M.probe.info().estimated_cardinality +
5571 1.0 * M.join.info().estimated_cardinality;
5572}
5573
5575{
5576 ConditionSet post_cond;
5577
5578 /*----- Hash-based group-join does not introduce predication (it is already handled by the hash table). -----*/
5579 post_cond.add_condition(Predicated(false));
5580
5581 /*----- Hash-based group-join does not introduce SIMD. -----*/
5582 post_cond.add_condition(NoSIMD());
5583
5584 return post_cond;
5585}
5586
5588 teardown_t teardown)
5589{
5590 // TODO: determine setup
5591 const uint64_t AGGREGATES_SIZE_THRESHOLD_IN_BITS =
5592 M.use_in_place_values ? std::numeric_limits<uint64_t>::max() : 0;
5593
5594 auto &C = Catalog::Get();
5595 const auto num_keys = M.grouping.group_by().size();
5596
5597 /*----- Compute hash table schema and information about aggregates, especially AVG aggregates. -----*/
5598 Schema ht_schema;
5599 for (std::size_t i = 0; i < num_keys; ++i) {
5600 auto &e = M.grouping.schema()[i];
5601 ht_schema.add(e.id, e.type, e.constraints);
5602 }
5603 auto aggregates_info = compute_aggregate_info(M.grouping.aggregates(), M.grouping.schema(), num_keys);
5604 const auto &aggregates = aggregates_info.first;
5605 const auto &avg_aggregates = aggregates_info.second;
5606 bool needs_build_counter = false;
5607 uint64_t aggregates_size_in_bits = 0;
5608 for (auto &info : aggregates) {
5609 ht_schema.add(info.entry);
5610 aggregates_size_in_bits += info.entry.type->size();
5611
5612 /* Add additional COUNT per group during build phase if COUNT or SUM dependent on probe relation occurs. */
5613 if (info.fnid == m::Function::FN_COUNT or info.fnid == m::Function::FN_SUM) {
5614 if (not info.args.empty()) {
5615 M_insist(info.args.size() == 1, "aggregate functions expect at most one argument");
5616 auto &des = as<const Designator>(*info.args[0]);
5617 Schema::Identifier arg(des.table_name.text, des.attr_name.text.assert_not_none());
5618 if (M.probe.schema().has(arg))
5619 needs_build_counter = true;
5620 }
5621 }
5622 }
5623 if (needs_build_counter) {
5624 ht_schema.add(Schema::Identifier(C.pool("$build_counter")), Type::Get_Integer(Type::TY_Scalar, 8),
5626 aggregates_size_in_bits += 64;
5627 }
5628 ht_schema.add(Schema::Identifier(C.pool("$probe_counter")), Type::Get_Integer(Type::TY_Scalar, 8),
5630 aggregates_size_in_bits += 64;
5631
5632 /*----- Decompose each clause of the join predicate of the form `A.x = B.y` into parts `A.x` and `B.y`. -----*/
5633 const auto [build_keys, probe_keys] = decompose_equi_predicate(M.join.predicate(), M.build.schema());
5634 M_insist(build_keys.size() == num_keys);
5635
5636 /*----- Compute initial capacity of hash table. -----*/
5637 uint32_t initial_capacity = compute_initial_ht_capacity(M.grouping, M.load_factor);
5638
5639 /*----- Create hash table for build relation. -----*/
5640 std::unique_ptr<HashTable> ht;
5641 std::vector<HashTable::index_t> key_indices(num_keys);
5642 std::iota(key_indices.begin(), key_indices.end(), 0);
5643 if (M.use_open_addressing_hashing) {
5644 if (aggregates_size_in_bits < AGGREGATES_SIZE_THRESHOLD_IN_BITS)
5645 ht = std::make_unique<GlobalOpenAddressingInPlaceHashTable>(ht_schema, std::move(key_indices),
5646 initial_capacity);
5647 else
5648 ht = std::make_unique<GlobalOpenAddressingOutOfPlaceHashTable>(ht_schema, std::move(key_indices),
5649 initial_capacity);
5650 if (M.use_quadratic_probing)
5651 as<OpenAddressingHashTableBase>(*ht).set_probing_strategy<QuadraticProbing>();
5652 else
5653 as<OpenAddressingHashTableBase>(*ht).set_probing_strategy<LinearProbing>();
5654 } else {
5655 ht = std::make_unique<GlobalChainedHashTable>(ht_schema, std::move(key_indices), initial_capacity);
5656 }
5657
5658 std::optional<HashTable::entry_t> dummy;
5659
5666 auto compile_aggregates = [&](HashTable::entry_t &entry, const Environment &env, const Schema &schema,
5667 bool build_phase) -> std::tuple<Block, Block, Block>
5668 {
5669 Block init_aggs("hash_based_group_join.init_aggs", false),
5670 update_aggs("hash_based_group_join.update_aggs", false),
5671 update_avg_aggs("hash_based_group_join.update_avg_aggs", false);
5672 for (auto &info : aggregates) {
5673 bool is_min = false;
5674 switch (info.fnid) {
5675 default:
5676 M_unreachable("unsupported aggregate function");
5677 case m::Function::FN_MIN:
5678 is_min = true; // set flag and delegate to MAX case
5679 case m::Function::FN_MAX: {
5680 M_insist(info.args.size() == 1, "MIN and MAX aggregate functions expect exactly one argument");
5681 auto &arg = as<const Designator>(*info.args[0]);
5682 const bool bound = schema.has(Schema::Identifier(arg.table_name.text,
5683 arg.attr_name.text.assert_not_none()));
5684
5685 std::visit(overloaded {
5686 [&]<sql_type _T>(HashTable::reference_t<_T> &&r) -> void
5687 requires (not (std::same_as<_T, _Boolx1> or std::same_as<_T, NChar>)) {
5688 using type = typename _T::type;
5689 using T = PrimitiveExpr<type>;
5690
5691 if (build_phase) {
5692 BLOCK_OPEN(init_aggs) {
5693 auto neutral = is_min ? T(std::numeric_limits<type>::max())
5694 : T(std::numeric_limits<type>::lowest());
5695 if (bound) {
5696 auto _arg = env.compile(arg);
5697 auto [val_, is_null] = convert<_T>(_arg).split();
5698 T val(val_); // due to structured binding and lambda closure
5699 IF (is_null) {
5700 r.clone().set_value(neutral); // initialize with neutral element +inf or -inf
5701 if (info.entry.nullable())
5702 r.clone().set_null_bit(Boolx1(true)); // first value is NULL
5703 } ELSE {
5704 r.clone().set_value(val); // initialize with first value
5705 if (info.entry.nullable())
5706 r.clone().set_null_bit(Boolx1(false)); // first value is not NULL
5707 };
5708 } else {
5709 r.clone().set_value(neutral); // initialize with neutral element +inf or -inf
5710 if (info.entry.nullable())
5711 r.clone().set_null_bit(Boolx1(true)); // initialize with neutral element NULL
5712 }
5713 }
5714 }
5715 if (not bound) {
5716 r.discard();
5717 return; // MIN and MAX does not change in phase when argument is unbound
5718 }
5719 BLOCK_OPEN(update_aggs) {
5720 auto _arg = env.compile(arg);
5721 _T _new_val = convert<_T>(_arg);
5722 if (_new_val.can_be_null()) {
5723 auto [new_val_, new_val_is_null_] = _new_val.split();
5724 auto [old_min_max_, old_min_max_is_null] = _T(r.clone()).split();
5725 const Var<Boolx1> new_val_is_null(new_val_is_null_); // due to multiple uses
5726
5727 auto chosen_r = Select(new_val_is_null, dummy->extract<_T>(info.entry.id), r.clone());
5728 if constexpr (std::floating_point<type>) {
5729 chosen_r.set_value(
5730 is_min ? min(old_min_max_, new_val_) // update old min with new value
5731 : max(old_min_max_, new_val_) // update old max with new value
5732 ); // if new value is NULL, only dummy is written
5733 } else {
5734 const Var<T> new_val(new_val_),
5735 old_min_max(old_min_max_); // due to multiple uses
5736 auto cmp = is_min ? new_val < old_min_max : new_val > old_min_max;
5737 chosen_r.set_value(
5738 Select(cmp,
5739 new_val, // update to new value
5740 old_min_max) // do not update
5741 ); // if new value is NULL, only dummy is written
5742 }
5743 r.set_null_bit(
5744 old_min_max_is_null and new_val_is_null // MIN/MAX is NULL iff all values are NULL
5745 );
5746 } else {
5747 auto new_val_ = _new_val.insist_not_null();
5748 auto old_min_max_ = _T(r.clone()).insist_not_null();
5749 if constexpr (std::floating_point<type>) {
5750 r.set_value(
5751 is_min ? min(old_min_max_, new_val_) // update old min with new value
5752 : max(old_min_max_, new_val_) // update old max with new value
5753 );
5754 } else {
5755 const Var<T> new_val(new_val_),
5756 old_min_max(old_min_max_); // due to multiple uses
5757 auto cmp = is_min ? new_val < old_min_max : new_val > old_min_max;
5758 r.set_value(
5759 Select(cmp,
5760 new_val, // update to new value
5761 old_min_max) // do not update
5762 );
5763 }
5764 /* do not update NULL bit since it is already set to `false` */
5765 }
5766 }
5767 },
5768 []<sql_type _T>(HashTable::reference_t<_T>&&) -> void
5769 requires std::same_as<_T,_Boolx1> or std::same_as<_T, NChar> {
5770 M_unreachable("invalid type");
5771 },
5772 [](std::monostate) -> void { M_unreachable("invalid reference"); },
5773 }, entry.extract(info.entry.id));
5774 break;
5775 }
5776 case m::Function::FN_AVG: {
5777 auto it = avg_aggregates.find(info.entry.id);
5778 M_insist(it != avg_aggregates.end());
5779 const auto &avg_info = it->second;
5780 M_insist(avg_info.compute_running_avg,
5781 "AVG aggregate may only occur for running average computations");
5782 M_insist(info.args.size() == 1, "AVG aggregate function expects exactly one argument");
5783 auto &arg = as<const Designator>(*info.args[0]);
5784 const bool bound = schema.has(Schema::Identifier(arg.table_name.text,
5785 arg.attr_name.text.assert_not_none()));
5786
5787 auto r = entry.extract<_Doublex1>(info.entry.id);
5788
5789 if (build_phase) {
5790 BLOCK_OPEN(init_aggs) {
5791 if (bound) {
5792 auto _arg = env.compile(arg);
5793 auto [val_, is_null] = convert<_Doublex1>(_arg).split();
5794 Doublex1 val(val_); // due to structured binding and lambda closure
5795 IF (is_null) {
5796 r.clone().set_value(Doublex1(0.0)); // initialize with neutral element 0
5797 if (info.entry.nullable())
5798 r.clone().set_null_bit(Boolx1(true)); // first value is NULL
5799 } ELSE {
5800 r.clone().set_value(val); // initialize with first value
5801 if (info.entry.nullable())
5802 r.clone().set_null_bit(Boolx1(false)); // first value is not NULL
5803 };
5804 } else {
5805 r.clone().set_value(Doublex1(0.0)); // initialize with neutral element 0
5806 if (info.entry.nullable())
5807 r.clone().set_null_bit(Boolx1(true)); // initialize with neutral element NULL
5808 }
5809 }
5810 }
5811 if (not bound) {
5812 r.discard();
5813 break; // AVG does not change in phase when argument is unbound
5814 }
5815 BLOCK_OPEN(update_avg_aggs) {
5816 /* Compute AVG as iterative mean as described in Knuth, The Art of Computer Programming
5817 * Vol 2, section 4.2.2. */
5818 auto _arg = env.compile(arg);
5819 _Doublex1 _new_val = convert<_Doublex1>(_arg);
5820 if (_new_val.can_be_null()) {
5821 auto [new_val, new_val_is_null_] = _new_val.split();
5822 auto [old_avg_, old_avg_is_null] = _Doublex1(r.clone()).split();
5823 const Var<Boolx1> new_val_is_null(new_val_is_null_); // due to multiple uses
5824 const Var<Doublex1> old_avg(old_avg_); // due to multiple uses
5825
5826 auto delta_absolute = new_val - old_avg;
5827 auto running_count = _I64x1(entry.get<_I64x1>(avg_info.running_count)).insist_not_null();
5828 auto delta_relative = delta_absolute / running_count.to<double>();
5829
5830 auto chosen_r = Select(new_val_is_null, dummy->extract<_Doublex1>(info.entry.id), r.clone());
5831 chosen_r.set_value(
5832 old_avg + delta_relative // update old average with new value
5833 ); // if new value is NULL, only dummy is written
5834 r.set_null_bit(
5835 old_avg_is_null and new_val_is_null // AVG is NULL iff all values are NULL
5836 );
5837 } else {
5838 auto new_val = _new_val.insist_not_null();
5839 auto old_avg_ = _Doublex1(r.clone()).insist_not_null();
5840 const Var<Doublex1> old_avg(old_avg_); // due to multiple uses
5841
5842 auto delta_absolute = new_val - old_avg;
5843 auto running_count = _I64x1(entry.get<_I64x1>(avg_info.running_count)).insist_not_null();
5844 auto delta_relative = delta_absolute / running_count.to<double>();
5845 r.set_value(
5846 old_avg + delta_relative // update old average with new value
5847 );
5848 /* do not update NULL bit since it is already set to `false` */
5849 }
5850 }
5851 break;
5852 }
5853 case m::Function::FN_SUM: {
5854 M_insist(info.args.size() == 1, "SUM aggregate function expects exactly one argument");
5855 auto &arg = as<const Designator>(*info.args[0]);
5856 const bool bound = schema.has(Schema::Identifier(arg.table_name.text,
5857 arg.attr_name.text.assert_not_none()));
5858
5859 std::visit(overloaded {
5860 [&]<sql_type _T>(HashTable::reference_t<_T> &&r) -> void
5861 requires (not (std::same_as<_T, _Boolx1> or std::same_as<_T, NChar>)) {
5862 using type = typename _T::type;
5863 using T = PrimitiveExpr<type>;
5864
5865 if (build_phase) {
5866 BLOCK_OPEN(init_aggs) {
5867 if (bound) {
5868 auto _arg = env.compile(arg);
5869 auto [val_, is_null] = convert<_T>(_arg).split();
5870 T val(val_); // due to structured binding and lambda closure
5871 IF (is_null) {
5872 r.clone().set_value(T(type(0))); // initialize with neutral element 0
5873 if (info.entry.nullable())
5874 r.clone().set_null_bit(Boolx1(true)); // first value is NULL
5875 } ELSE {
5876 r.clone().set_value(val); // initialize with first value
5877 if (info.entry.nullable())
5878 r.clone().set_null_bit(Boolx1(false)); // first value is not NULL
5879 };
5880 } else {
5881 r.clone().set_value(T(type(0))); // initialize with neutral element 0
5882 if (info.entry.nullable())
5883 r.clone().set_null_bit(Boolx1(true)); // initialize with neutral element NULL
5884 }
5885 }
5886 }
5887 if (not bound) {
5888 r.discard();
5889 return; // SUM may later be multiplied with group counter but does not change here
5890 }
5891 BLOCK_OPEN(update_aggs) {
5892 auto _arg = env.compile(arg);
5893 _T _new_val = convert<_T>(_arg);
5894 if (_new_val.can_be_null()) {
5895 auto [new_val, new_val_is_null_] = _new_val.split();
5896 auto [old_sum, old_sum_is_null] = _T(r.clone()).split();
5897 const Var<Boolx1> new_val_is_null(new_val_is_null_); // due to multiple uses
5898
5899 auto chosen_r = Select(new_val_is_null, dummy->extract<_T>(info.entry.id), r.clone());
5900 chosen_r.set_value(
5901 old_sum + new_val // add new value to old sum
5902 ); // if new value is NULL, only dummy is written
5903 r.set_null_bit(
5904 old_sum_is_null and new_val_is_null // SUM is NULL iff all values are NULL
5905 );
5906 } else {
5907 auto new_val = _new_val.insist_not_null();
5908 auto old_sum = _T(r.clone()).insist_not_null();
5909 r.set_value(
5910 old_sum + new_val // add new value to old sum
5911 );
5912 /* do not update NULL bit since it is already set to `false` */
5913 }
5914 }
5915 },
5916 []<sql_type _T>(HashTable::reference_t<_T>&&) -> void
5917 requires std::same_as<_T,_Boolx1> or std::same_as<_T, NChar> {
5918 M_unreachable("invalid type");
5919 },
5920 [](std::monostate) -> void { M_unreachable("invalid reference"); },
5921 }, entry.extract(info.entry.id));
5922 break;
5923 }
5924 case m::Function::FN_COUNT: {
5925 M_insist(info.args.size() <= 1, "COUNT aggregate function expects at most one argument");
5926
5927 auto r = entry.get<_I64x1>(info.entry.id); // do not extract to be able to access for AVG case
5928
5929 if (info.args.empty()) {
5930 if (not build_phase) {
5931 r.discard();
5932 break; // COUNT(*) will later be multiplied with probe counter but only changes in build phase
5933 }
5934 BLOCK_OPEN(init_aggs) {
5935 r.clone() = _I64x1(1); // initialize with 1 (for first value)
5936 }
5937 BLOCK_OPEN(update_aggs) {
5938 auto old_count = _I64x1(r.clone()).insist_not_null();
5939 r.set_value(
5940 old_count + int64_t(1) // increment old count by 1
5941 );
5942 /* do not update NULL bit since it is already set to `false` */
5943 }
5944 } else {
5945 auto &arg = as<const Designator>(*info.args[0]);
5946 const bool bound = schema.has(Schema::Identifier(arg.table_name.text,
5947 arg.attr_name.text.assert_not_none()));
5948
5949 if (build_phase) {
5950 BLOCK_OPEN(init_aggs) {
5951 if (bound) {
5952 auto _arg = env.compile(arg);
5953 I64x1 new_val_not_null =
5954 can_be_null(_arg) ? not_null(_arg).to<int64_t>()
5955 : (discard(_arg), I64x1(1)); // discard since no use
5956 r.clone() = _I64x1(new_val_not_null); // initialize with 1 iff first value is present
5957 } else {
5958 r.clone() = _I64x1(0); // initialize with neutral element 0
5959 }
5960 }
5961 }
5962 if (not bound) {
5963 r.discard();
5964 break; // COUNT may later be multiplied with group counter but does not change here
5965 }
5966 BLOCK_OPEN(update_aggs) {
5967 auto _arg = env.compile(arg);
5968 I64x1 new_val_not_null =
5969 can_be_null(_arg) ? not_null(_arg).to<int64_t>()
5970 : (discard(_arg), I64x1(1)); // discard since no use
5971 auto old_count = _I64x1(r.clone()).insist_not_null();
5972 r.set_value(
5973 old_count + new_val_not_null // increment old count by 1 iff new value is present
5974 );
5975 /* do not update NULL bit since it is already set to `false` */
5976 }
5977 }
5978 break;
5979 }
5980 }
5981 }
5982 return { std::move(init_aggs), std::move(update_aggs), std::move(update_avg_aggs) };
5983 };
5984
5985 /*----- Create function for build child. -----*/
5986 FUNCTION(hash_based_group_join_build_child_pipeline, void(void)) // create function for pipeline
5987 {
5988 auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
5989
5990 M.children[0]->execute(
5991 /* setup= */ setup_t::Make_Without_Parent([&](){
5992 ht->setup();
5993 ht->set_high_watermark(M.load_factor);
5994 dummy.emplace(ht->dummy_entry()); // create dummy slot to ignore NULL values in aggregate computations
5995 }),
5996 /* pipeline= */ [&](){
5997 M_insist(bool(dummy));
5998 const auto &env = CodeGenContext::Get().env();
5999
6000 std::optional<Boolx1> build_key_not_null;
6001 for (auto &build_key : build_keys) {
6002 auto val = env.get(build_key);
6003 if (build_key_not_null)
6004 build_key_not_null.emplace(*build_key_not_null and not_null(val));
6005 else
6006 build_key_not_null.emplace(not_null(val));
6007 }
6008 M_insist(bool(build_key_not_null));
6009 IF (*build_key_not_null) { // TODO: predicated version
6010 /*----- Insert key if not yet done. -----*/
6011 std::vector<SQL_t> key;
6012 for (auto &build_key : build_keys)
6013 key.emplace_back(env.get(build_key));
6014 auto [entry, inserted] = ht->try_emplace(std::move(key));
6015
6016 /*----- Compile aggregates. -----*/
6017 auto t = compile_aggregates(entry, env, M.build.schema(), /* build_phase= */ true);
6018 auto &init_aggs = std::get<0>(t);
6019 auto &update_aggs = std::get<1>(t);
6020 auto &update_avg_aggs = std::get<2>(t);
6021
6022 /*----- Add group counters to compiled aggregates. -----*/
6023 if (needs_build_counter) {
6024 auto r = entry.extract<_I64x1>(C.pool("$build_counter"));
6025 BLOCK_OPEN(init_aggs) {
6026 r.clone() = _I64x1(1); // initialize with 1 (for first value)
6027 }
6028 BLOCK_OPEN(update_aggs) {
6029 auto old_count = _I64x1(r.clone()).insist_not_null();
6030 r.set_value(
6031 old_count + int64_t(1) // increment old count by 1
6032 );
6033 /* do not update NULL bit since it is already set to `false` */
6034 }
6035 }
6036 BLOCK_OPEN(init_aggs) {
6037 auto r = entry.extract<_I64x1>(C.pool("$probe_counter"));
6038 r = _I64x1(0); // initialize with neutral element 0
6039 }
6040
6041 /*----- If group has been inserted, initialize aggregates. Otherwise, update them. -----*/
6042 IF (inserted) {
6043 init_aggs.attach_to_current();
6044 } ELSE {
6045 update_aggs.attach_to_current();
6046 update_avg_aggs.attach_to_current(); // after others to ensure that running count is incremented before
6047 };
6048 };
6049 },
6050 /* teardown= */ teardown_t::Make_Without_Parent([&](){ ht->teardown(); })
6051 );
6052 }
6053 hash_based_group_join_build_child_pipeline(); // call build child function
6054
6055 /*----- Create function for probe child. -----*/
6056 FUNCTION(hash_based_group_join_probe_child_pipeline, void(void)) // create function for pipeline
6057 {
6058 auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
6059
6060 M.children[1]->execute(
6061 /* setup= */ setup_t::Make_Without_Parent([&](){
6062 ht->setup();
6063 dummy.emplace(ht->dummy_entry()); // create dummy slot to ignore NULL values in aggregate computations
6064 }),
6065 /* pipeline= */ [&](){
6066 M_insist(bool(dummy));
6067 const auto &env = CodeGenContext::Get().env();
6068
6069 /* TODO: may check for NULL on probe keys as well, branching + predicated version */
6070 /*----- Probe with probe key. -----*/
6071 std::vector<SQL_t> key;
6072 for (auto &probe_key : probe_keys)
6073 key.emplace_back(env.get(probe_key));
6074 auto [entry, found] = ht->find(std::move(key));
6075
6076 /*----- Compile aggregates. -----*/
6077 auto t = compile_aggregates(entry, env, M.probe.schema(), /* build_phase= */ false);
6078 auto &init_aggs = std::get<0>(t);
6079 auto &update_aggs = std::get<1>(t);
6080 auto &update_avg_aggs = std::get<2>(t);
6081
6082 /*----- Add probe counter to compiled aggregates. -----*/
6083 BLOCK_OPEN(update_aggs) {
6084 auto r = entry.extract<_I64x1>(C.pool("$probe_counter"));
6085 auto old_count = _I64x1(r.clone()).insist_not_null();
6086 r.set_value(
6087 old_count + int64_t(1) // increment old count by 1
6088 );
6089 /* do not update NULL bit since it is already set to `false` */
6090 }
6091
6092 /*----- If group has been inserted, initialize aggregates. Otherwise, update them. -----*/
6093 M_insist(init_aggs.empty(), "aggregates must be initialized in build phase");
6094 IF (found) {
6095 update_aggs.attach_to_current();
6096 update_avg_aggs.attach_to_current(); // after others to ensure that running count is incremented before
6097 };
6098 },
6099 /* teardown= */ teardown_t::Make_Without_Parent([&](){ ht->teardown(); })
6100 );
6101 }
6102 hash_based_group_join_probe_child_pipeline(); // call probe child function
6103
6104 auto &env = CodeGenContext::Get().env();
6105
6106 /*----- Process each computed group. -----*/
6107 setup_t(std::move(setup), [&](){ ht->setup(); })();
6108 ht->for_each([&, pipeline=std::move(pipeline)](HashTable::const_entry_t entry){
6109 /*----- Check whether probe match was found. -----*/
6110 I64x1 probe_counter = _I64x1(entry.get<_I64x1>(C.pool("$probe_counter"))).insist_not_null();
6111 IF (probe_counter != int64_t(0)) {
6112 /*----- Compute key schema to detect duplicated keys. -----*/
6113 Schema key_schema;
6114 for (std::size_t i = 0; i < num_keys; ++i) {
6115 auto &e = M.grouping.schema()[i];
6116 key_schema.add(e.id, e.type, e.constraints);
6117 }
6118
6119 /*----- Add computed group tuples to current environment. ----*/
6120 for (auto &e : M.grouping.schema().deduplicate()) {
6121 try {
6122 key_schema.find(e.id);
6123 } catch (invalid_argument&) {
6124 continue; // skip duplicated keys since they must not be used afterwards
6125 }
6126
6127 if (auto it = avg_aggregates.find(e.id);
6128 it != avg_aggregates.end() and not it->second.compute_running_avg)
6129 { // AVG aggregates which is not yet computed, divide computed sum with computed count
6130 auto &avg_info = it->second;
6131 auto sum = std::visit(overloaded {
6132 [&]<sql_type T>(HashTable::const_reference_t<T> &&r) -> _Doublex1
6133 requires (std::same_as<T, _I64x1> or std::same_as<T, _Doublex1>) {
6134 return T(r).template to<double>();
6135 },
6136 [](auto&&) -> _Doublex1 { M_unreachable("invalid type"); },
6137 [](std::monostate&&) -> _Doublex1 { M_unreachable("invalid reference"); },
6138 }, entry.get(avg_info.sum));
6139 auto count = _I64x1(entry.get<_I64x1>(avg_info.running_count)).insist_not_null().to<double>();
6140 auto avg = sum / count; // no need to multiply with group counter as the factor would not change the fraction
6141 if (avg.can_be_null()) {
6142 _Var<Doublex1> var(avg); // introduce variable s.t. uses only load from it
6143 env.add(e.id, var);
6144 } else {
6145 /* introduce variable w/o NULL bit s.t. uses only load from it */
6146 Var<Doublex1> var(avg.insist_not_null());
6147 env.add(e.id, _Doublex1(var));
6148 }
6149 } else { // part of key or already computed aggregate (without multiplication with group counter)
6150 std::visit(overloaded {
6151 [&]<typename T>(HashTable::const_reference_t<Expr<T>> &&r) -> void {
6152 Expr<T> value = r;
6153
6154 auto pred = [&e](const auto &info) -> bool { return info.entry.id == e.id; };
6155 if (auto it = std::find_if(aggregates.cbegin(), aggregates.cend(), pred);
6156 it != aggregates.cend())
6157 { // aggregate
6158 /* For COUNT and SUM, multiply current aggregate value with respective group counter
6159 * since only tuples in phase in which argument is bound are counted/summed up. */
6160 if (it->args.empty()) {
6161 M_insist(it->fnid == m::Function::FN_COUNT,
6162 "only COUNT aggregate function may have no argument");
6163 I64x1 probe_counter =
6164 _I64x1(entry.get<_I64x1>(C.pool("$probe_counter"))).insist_not_null();
6165 PrimitiveExpr<T> count = value.insist_not_null() * probe_counter.to<T>();
6166 Var<PrimitiveExpr<T>> var(count); // introduce variable s.t. uses only load from it
6167 env.add(e.id, Expr<T>(var));
6168 return; // next group tuple entry
6169 } else {
6170 M_insist(it->args.size() == 1, "aggregate functions expect at most one argument");
6171 auto &des = as<const Designator>(*it->args[0]);
6172 Schema::Identifier arg(des.table_name.text, des.attr_name.text.assert_not_none());
6173 if (it->fnid == m::Function::FN_COUNT or it->fnid == m::Function::FN_SUM) {
6174 if (M.probe.schema().has(arg)) {
6175 I64x1 build_counter =
6176 _I64x1(entry.get<_I64x1>(C.pool("$build_counter"))).insist_not_null();
6177 auto agg = value * build_counter.to<T>();
6178 if (agg.can_be_null()) {
6179 Var<Expr<T>> var(agg); // introduce variable s.t. uses only load from it
6180 env.add(e.id, var);
6181 } else {
6182 /* introduce variable w/o NULL bit s.t. uses only load from it */
6183 Var<PrimitiveExpr<T>> var(agg.insist_not_null());
6184 env.add(e.id, Expr<T>(var));
6185 }
6186 } else {
6187 M_insist(M.build.schema().has(arg),
6188 "argument ID must occur in either child schema");
6189 I64x1 probe_counter =
6190 _I64x1(entry.get<_I64x1>(C.pool("$probe_counter"))).insist_not_null();
6191 auto agg = value * probe_counter.to<T>();
6192 if (agg.can_be_null()) {
6193 Var<Expr<T>> var(agg); // introduce variable s.t. uses only load from it
6194 env.add(e.id, var);
6195 } else {
6196 /* introduce variable w/o NULL bit s.t. uses only load from it */
6197 Var<PrimitiveExpr<T>> var(agg.insist_not_null());
6198 env.add(e.id, Expr<T>(var));
6199 }
6200 }
6201 return; // next group tuple entry
6202 }
6203 }
6204 }
6205
6206 /* fallthrough: part of key or correctly computed aggregate */
6207 if (value.can_be_null()) {
6208 Var<Expr<T>> var(value); // introduce variable s.t. uses only load from it
6209 env.add(e.id, var);
6210 } else {
6211 /* introduce variable w/o NULL bit s.t. uses only load from it */
6212 Var<PrimitiveExpr<T>> var(value.insist_not_null());
6213 env.add(e.id, Expr<T>(var));
6214 }
6215 },
6216 [&](HashTable::const_reference_t<_Boolx1> &&r) -> void {
6217#ifndef NDEBUG
6218 auto pred = [&e](const auto &info) -> bool { return info.entry.id == e.id; };
6219 M_insist(std::find_if(aggregates.cbegin(), aggregates.cend(), pred) == aggregates.cend(),
6220 "booleans must not be the result of aggregate functions");
6221#endif
6222 _Boolx1 value = r;
6223 if (value.can_be_null()) {
6224 _Var<Boolx1> var(value); // introduce variable s.t. uses only load from it
6225 env.add(e.id, var);
6226 } else {
6227 /* introduce variable w/o NULL bit s.t. uses only load from it */
6228 Var<Boolx1> var(value.insist_not_null());
6229 env.add(e.id, _Boolx1(var));
6230 }
6231 },
6232 [&](HashTable::const_reference_t<NChar> &&r) -> void {
6233#ifndef NDEBUG
6234 auto pred = [&e](const auto &info) -> bool { return info.entry.id == e.id; };
6235 M_insist(std::find_if(aggregates.cbegin(), aggregates.cend(), pred) == aggregates.cend(),
6236 "strings must not be the result of aggregate functions");
6237#endif
6238 NChar value(r);
6239 Var<Ptr<Charx1>> var(value.val()); // introduce variable s.t. uses only load from it
6240 env.add(e.id, NChar(var, value.can_be_null(), value.length(),
6241 value.guarantees_terminating_nul()));
6242 },
6243 [](std::monostate&&) -> void { M_unreachable("invalid reference"); },
6244 }, entry.get(e.id)); // do not extract to be able to access for not-yet-computed AVG aggregates
6245 }
6246 }
6247
6248 /*----- Resume pipeline. -----*/
6249 pipeline();
6250 };
6251 });
6252 teardown_t(std::move(teardown), [&](){ ht->teardown(); })();
6253}
6254
6255
6256/*======================================================================================================================
6257 * Match<T>::print()
6258 *====================================================================================================================*/
6259
6261{
6262 const Operator &op;
6263
6264 friend std::ostream & operator<<(std::ostream &out, const print_info &info) {
6265 if (info.op.has_info())
6266 out << " <" << info.op.info().estimated_cardinality << '>';
6267 return out;
6268 }
6269};
6270
6271void Match<m::wasm::NoOp>::print(std::ostream &out, unsigned level) const
6272{
6273 indent(out, level) << "wasm::NoOp" << print_info(this->noop) << " (cumulative cost " << cost() << ')';
6274 this->child->print(out, level + 1);
6275}
6276
6277template<bool SIMDfied>
6278void Match<m::wasm::Callback<SIMDfied>>::print(std::ostream &out, unsigned level) const
6279{
6280 indent(out, level) << "wasm::Callback with " << this->result_set_window_size << " tuples result set "
6281 << this->callback.schema() << print_info(this->callback)
6282 << " (cumulative cost " << cost() << ')';
6283 this->child->print(out, level + 1);
6284}
6285
6286template<bool SIMDfied>
6287void Match<m::wasm::Print<SIMDfied>>::print(std::ostream &out, unsigned level) const
6288{
6289 indent(out, level) << "wasm::Print with " << this->result_set_window_size << " tuples result set "
6290 << this->print_op.schema() << print_info(this->print_op)
6291 << " (cumulative cost " << cost() << ')';
6292 this->child->print(out, level + 1);
6293}
6294
6295template<bool SIMDfied>
6296void Match<m::wasm::Scan<SIMDfied>>::print(std::ostream &out, unsigned level) const
6297{
6298 indent(out, level) << (SIMDfied ? "wasm::SIMDScan(" : "wasm::Scan(") << this->scan.alias() << ") ";
6299 if (this->buffer_factory_ and this->scan.schema().drop_constants().deduplicate().num_entries())
6300 out << "with " << this->buffer_num_tuples_ << " tuples output buffer ";
6301 out << this->scan.schema() << print_info(this->scan) << " (cumulative cost " << cost() << ')';
6302}
6303
6304template<idx::IndexMethod IndexMethod>
6305void Match<m::wasm::IndexScan<IndexMethod>>::print(std::ostream &out, unsigned level) const
6306{
6307 if (IndexMethod == idx::IndexMethod::Array)
6308 indent(out, level) << "wasm::ArrayIndexScan(";
6309 else if (IndexMethod == idx::IndexMethod::Rmi)
6310 indent(out, level) << "wasm::RecursiveModelIndexScan(";
6311 else
6312 M_unreachable("unknown index");
6313
6315 out << "Compilation[";
6317 out << "Callback";
6319 out << "ExposedMemory";
6320 else
6321 M_unreachable("unknown compilation strategy");
6323 out << "Interpretation[";
6325 out << "Inline";
6327 out << "Memory";
6328 else
6329 M_unreachable("unknown materialization strategy");
6331 out << "Hybrid[";
6333 out << "Inline,";
6335 out << "Memory,";
6336 else
6337 M_unreachable("unknown materialization strategy");
6339 out << "Callback";
6341 out << "ExposedMemory";
6342 else
6343 M_unreachable("unknown compilation strategy");
6344 } else {
6345 M_unreachable("unknown strategy");
6346 }
6347
6348 out << "], " << this->scan.alias() << ", " << this->filter.filter() << ") ";
6349 if (this->buffer_factory_ and this->scan.schema().drop_constants().deduplicate().num_entries())
6350 out << "with " << this->buffer_num_tuples_ << " tuples output buffer ";
6351 out << this->scan.schema() << print_info(this->scan) << " (cumulative cost " << cost() << ')';
6352}
6353
6354template<bool Predicated>
6355void Match<m::wasm::Filter<Predicated>>::print(std::ostream &out, unsigned level) const
6356{
6357 indent(out, level) << "wasm::" << (Predicated ? "Predicated" : "Branching") << "Filter ";
6358 if (this->buffer_factory_ and this->filter.schema().drop_constants().deduplicate().num_entries())
6359 out << "with " << this->buffer_num_tuples_ << " tuples output buffer ";
6360 out << this->filter.schema() << print_info(this->filter) << " (cumulative cost " << cost() << ')';
6361 this->child->print(out, level + 1);
6362}
6363
6364void Match<m::wasm::LazyDisjunctiveFilter>::print(std::ostream &out, unsigned level) const
6365{
6366 indent(out, level) << "wasm::LazyDisjunctiveFilter ";
6367 if (this->buffer_factory_ and this->filter.schema().drop_constants().deduplicate().num_entries())
6368 out << "with " << this->buffer_num_tuples_ << " tuples output buffer ";
6369 const cnf::Clause &clause = this->filter.filter()[0];
6370 for (auto it = clause.cbegin(); it != clause.cend(); ++it) {
6371 if (it != clause.cbegin()) out << " → ";
6372 out << *it;
6373 }
6374 out << ' ' << this->filter.schema() << print_info(this->filter) << " (cumulative cost " << cost() << ')';
6375 this->child->print(out, level + 1);
6376}
6377
6378void Match<m::wasm::Projection>::print(std::ostream &out, unsigned level) const
6379{
6380 indent(out, level) << "wasm::Projection ";
6381 if (this->buffer_factory_ and this->projection.schema().drop_constants().deduplicate().num_entries())
6382 out << "with " << this->buffer_num_tuples_ << " tuples output buffer ";
6383 out << this->projection.schema() << print_info(this->projection) << " (cumulative cost " << cost() << ')';
6384 if (this->child)
6385 this->child->get()->print(out, level + 1);
6386}
6387
6388void Match<m::wasm::HashBasedGrouping>::print(std::ostream &out, unsigned level) const
6389{
6390 indent(out, level) << "wasm::HashBasedGrouping " << this->grouping.schema() << print_info(this->grouping)
6391 << " (cumulative cost " << cost() << ')';
6392 this->child->print(out, level + 1);
6393}
6394
6395void Match<m::wasm::OrderedGrouping>::print(std::ostream &out, unsigned level) const
6396{
6397 indent(out, level) << "wasm::OrderedGrouping " << this->grouping.schema() << print_info(this->grouping)
6398 << " (cumulative cost " << cost() << ')';
6399 this->child->print(out, level + 1);
6400}
6401
6402void Match<m::wasm::Aggregation>::print(std::ostream &out, unsigned level) const
6403{
6404 indent(out, level) << "wasm::Aggregation " << this->aggregation.schema() << print_info(this->aggregation)
6405 << " (cumulative cost " << cost() << ')';
6406 this->child->print(out, level + 1);
6407}
6408
6409template<bool CmpPredicated>
6410void Match<m::wasm::Quicksort<CmpPredicated>>::print(std::ostream &out, unsigned level) const
6411{
6412 indent(out, level) << "wasm::" << (CmpPredicated ? "Predicated" : "") << "Quicksort " << this->sorting.schema()
6413 << print_info(this->sorting) << " (cumulative cost " << cost() << ')';
6414 this->child->print(out, level + 1);
6415}
6416
6417void Match<m::wasm::NoOpSorting>::print(std::ostream &out, unsigned level) const
6418{
6419 indent(out, level) << "wasm::NoOpSorting" << print_info(this->sorting) << " (cumulative cost " << cost() << ')';
6420 this->child->print(out, level + 1);
6421}
6422
6423template<bool Predicated>
6424void Match<m::wasm::NestedLoopsJoin<Predicated>>::print(std::ostream &out, unsigned level) const
6425{
6426 indent(out, level) << "wasm::" << (Predicated ? "Predicated" : "") << "NestedLoopsJoin ";
6427 if (this->buffer_factory_ and this->join.schema().drop_constants().deduplicate().num_entries())
6428 out << "with " << this->buffer_num_tuples_ << " tuples output buffer ";
6429 out << this->join.schema() << print_info(this->join) << " (cumulative cost " << cost() << ')';
6430
6431 ++level;
6432 std::size_t i = this->children.size();
6433 while (i--) {
6434 const m::wasm::MatchBase &child = *this->children[i];
6435 indent(out, level) << i << ". input";
6436 child.print(out, level + 1);
6437 }
6438}
6439
6440template<bool Unique, bool Predicated>
6441void Match<m::wasm::SimpleHashJoin<Unique, Predicated>>::print(std::ostream &out, unsigned level) const
6442{
6443 indent(out, level) << "wasm::" << (Predicated ? "Predicated" : "") << "SimpleHashJoin";
6444 if (Unique) out << " on UNIQUE key ";
6445 if (this->buffer_factory_ and this->join.schema().drop_constants().deduplicate().num_entries())
6446 out << "with " << this->buffer_num_tuples_ << " tuples output buffer ";
6447 out << this->join.schema() << print_info(this->join) << " (cumulative cost " << cost() << ')';
6448
6449 ++level;
6450 const m::wasm::MatchBase &build = *this->children[0];
6451 const m::wasm::MatchBase &probe = *this->children[1];
6452 indent(out, level) << "probe input";
6453 probe.print(out, level + 1);
6454 indent(out, level) << "build input";
6455 build.print(out, level + 1);
6456}
6457
6458template<bool SortLeft, bool SortRight, bool Predicated, bool CmpPredicated>
6460 unsigned level) const
6461{
6462 indent(out, level) << "wasm::" << (Predicated ? "Predicated" : "") << "SortMergeJoin ";
6463 switch ((unsigned(SortLeft) << 1) | unsigned(SortRight))
6464 {
6465 case 0: out << "pre-sorted "; break;
6466 case 1: out << "sorting right input " << (CmpPredicated ? "predicated " : ""); break;
6467 case 2: out << "sorting left input " << (CmpPredicated ? "predicated " : ""); break;
6468 case 3: out << "sorting both inputs " << (CmpPredicated ? "predicated " : ""); break;
6469 }
6470 const bool needs_buffer_parent = not is<const ScanOperator>(this->parent) or SortLeft;
6471 const bool needs_buffer_child = not is<const ScanOperator>(this->child) or SortRight;
6472 if (needs_buffer_parent and needs_buffer_child)
6473 out << "and materializing both inputs ";
6474 else if (needs_buffer_parent)
6475 out << "and materializing left input ";
6476 else if (needs_buffer_child)
6477 out << "and materializing right input ";
6478 out << this->join.schema() << print_info(this->join) << " (cumulative cost " << cost() << ')';
6479
6480 ++level;
6481 const m::wasm::MatchBase &left = *this->children[0];
6482 const m::wasm::MatchBase &right = *this->children[1];
6483 indent(out, level) << "right input";
6484 right.print(out, level + 1);
6485 indent(out, level) << "left input";
6486 left.print(out, level + 1);
6487}
6488
6489void Match<m::wasm::Limit>::print(std::ostream &out, unsigned level) const
6490{
6491 indent(out, level) << "wasm::Limit " << this->limit.schema() << print_info(this->limit)
6492 << " (cumulative cost " << cost() << ')';
6493 this->child->print(out, level + 1);
6494}
6495
6496void Match<m::wasm::HashBasedGroupJoin>::print(std::ostream &out, unsigned level) const
6497{
6498 indent(out, level) << "wasm::HashBasedGroupJoin ";
6499 if (this->buffer_factory_ and this->grouping.schema().drop_constants().deduplicate().num_entries())
6500 out << "with " << this->buffer_num_tuples_ << " tuples output buffer ";
6501 out << this->grouping.schema() << print_info(this->grouping) << " (cumulative cost " << cost() << ')';
6502
6503 ++level;
6504 const m::wasm::MatchBase &build = *this->children[0];
6505 const m::wasm::MatchBase &probe = *this->children[1];
6506 indent(out, level) << "probe input";
6507 probe.print(out, level + 1);
6508 indent(out, level) << "build input";
6509 build.print(out, level + 1);
6510}
6511
6512
6513/*======================================================================================================================
6514 * ThePreOrderMatchBaseVisitor, ThePostOrderMatchBaseVisitor
6515 *====================================================================================================================*/
6516
6517namespace {
6518
6519template<bool C, bool PreOrder>
6520struct recursive_matchbase_visitor : TheRecursiveMatchBaseVisitorBase<C>
6521{
6523 template<typename T> using Const = typename super::template Const<T>;
6524 using callback_t = std::conditional_t<C, ConstMatchBaseVisitor, MatchBaseVisitor>;
6525
6526 private:
6527 callback_t &callback_;
6528
6529 public:
6530 recursive_matchbase_visitor(callback_t &callback) : callback_(callback) { }
6531
6532 using super::operator();
6533#define DECLARE(CLASS) \
6534 void operator()(Const<CLASS> &M) override { \
6535 if constexpr (PreOrder) try { callback_(M); } catch (visit_skip_subtree) { return; } \
6536 super::operator()(M); \
6537 if constexpr (not PreOrder) callback_(M); \
6538 }
6540#undef DECLARE
6541};
6542
6543}
6544
6545template<bool C>
6547{
6548 recursive_matchbase_visitor<C, /* PreOrder= */ true>{*this}(M);
6549}
6550
6551template<bool C>
6553{
6554 recursive_matchbase_visitor<C, /* PreOrder= */ false>{*this}(M);
6555}
6556
6557
6558/*======================================================================================================================
6559 * Explicit template instantiations
6560 *====================================================================================================================*/
6561
6562#define INSTANTIATE(CLASS) \
6563 template struct m::wasm::CLASS; \
6564 template struct m::Match<m::wasm::CLASS>;
6566#undef INSTANTIATE
6567
__attribute__((constructor(202))) static void register_interpreter()
#define id(X)
#define DECLARE(CLASS)
#define M_insist_no_ternary_logic()
Definition: WasmDSL.hpp:45
#define Wasm_insist(...)
Definition: WasmDSL.hpp:373
#define ELSE
Definition: WasmMacro.hpp:24
#define WHILE(...)
Definition: WasmMacro.hpp:43
#define BLOCK(...)
Definition: WasmMacro.hpp:15
#define PARAMETER(IDX)
Definition: WasmMacro.hpp:20
#define IF(COND)
Definition: WasmMacro.hpp:23
#define FUNCTION(NAME, TYPE)
Definition: WasmMacro.hpp:17
#define BLOCK_OPEN(BLK)
Definition: WasmMacro.hpp:8
std::pair< const Constant &, bool > get_valid_bound(const ast::Expr &expr)
Given an Expr expr representing a valid bound, returns a pair consiting of a constant and a boolean f...
void index_scan_resolve_attribute_type(const Match< IndexScan< IndexMethod > > &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
Resolves the attribute type and calls the appropriate codegen function.
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)
Resolves the index scan strategy and calls the appropriate codegen function.
Ptr< void > get_array_index_base_address(const ThreadSafePooledString &table_name, const ThreadSafePooledString &attr_name)
Returns a pointer to the beginning of array index for table table_name and attribute attr_name.
std::pair< std::vector< Schema::Identifier >, std::vector< Schema::Identifier > > decompose_equi_predicate(const cnf::CNF &cnf, const Schema &schema_left)
Decompose the equi-predicate cnf, i.e.
uint32_t compute_initial_ht_capacity(const Operator &op, double load_factor)
Computes the initial hash table capacity for op.
#define RESOLVE_INDEX_METHOD(ATTRTYPE, SQLTYPE)
void write_result_set(const Schema &schema, const DataLayoutFactory &factory, uint32_t window_size, const m::wasm::MatchBase &child)
Emits code to write the result set of the Schema schema using the DataLayout created by factory.
U32x1 get_array_index_num_entries(const ThreadSafePooledString &table_name, const ThreadSafePooledString &attr_name)
Returns the number of entries of array index for table table_name and attribute attr_name.
void index_scan_codegen_compilation(const Index &index, const index_scan_bounds_t &bounds, const Match< IndexScan< IndexMethod > > &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
bool is_valid_bound(const ast::Expr &expr)
Returns true iff expr is a valid bound.
void index_scan_resolve_index_method(const index_scan_bounds_t &bounds, const Match< IndexScan< IndexMethod > > &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
Resolves the index method and calls the appropriate codegen function.
#define INSTANTIATE(CLASS)
Ptr< void > get_base_address(const ThreadSafePooledString &table_name)
Returns a pointer to the beginning of table table_name in the WebAssembly linear memory.
U32x1 get_num_rows(const ThreadSafePooledString &table_name)
Returns the number of rows of table table_name.
std::pair< std::vector< aggregate_info_t >, std::unordered_map< Schema::Identifier, avg_aggregate_info_t > > compute_aggregate_info(const std::vector< std::reference_wrapper< const FnApplicationExpr > > &aggregates, const Schema &schema, std::size_t aggregates_offset=0)
Computes and returns information about the aggregates aggregates which are contained in the schema sc...
void index_scan_codegen_hybrid(const Index &index, const index_scan_bounds_t &bounds, const Match< IndexScan< IndexMethod > > &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
void index_scan_codegen_interpretation(const Index &index, const index_scan_bounds_t &bounds, const Match< IndexScan< IndexMethod > > &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
#define RESOLVE_KEYTYPE(INDEX)
index_scan_bounds_t extract_index_scan_bounds(const cnf::CNF &cnf)
Extracts the bounds for performing index scan from CNF cnf.
#define M_WASM_OPERATOR_LIST_TEMPLATED(X)
#define M_WASM_MATCH_LIST(X)
void add(const char *group_name, const char *short_name, const char *long_name, const char *description, Callback &&callback)
Adds a new group option to the ArgParser.
Definition: ArgParser.hpp:84
Check whether.
Definition: concepts.hpp:14
Check whether.
Definition: concepts.hpp:23
#define M_unreachable(MSG)
Definition: macro.hpp:146
#define M_COMMA(X)
Definition: macro.hpp:23
#define M_CONSTEXPR_COND(COND, IF_TRUE, IF_FALSE)
Definition: macro.hpp:54
#define M_notnull(ARG)
Definition: macro.hpp:182
#define M_insist(...)
Definition: macro.hpp:129
std::ostream & indent(std::ostream &out, unsigned indentation)
Start a new line with proper indentation.
Definition: DataLayout.cpp:100
option_configs::OrderingStrategy simple_hash_join_ordering_strategy
Which ordering strategy should be used for wasm::SimpleHashJoin.
option_configs::IndexScanStrategy index_scan_strategy
Which index scan strategy should be used for wasm::IndexScan.
option_configs::IndexScanMaterializationStrategy index_scan_materialization_strategy
Which materialization strategy should be used for wasm::IndexScan.
std::vector< std::pair< m::Schema::Identifier, bool > > sorted_attributes
Which attributes are assumed to be sorted.
bool double_pumping
Whether to use double pumping if SIMDfication is enabled.
std::size_t result_set_window_size
Which window size should be used for the result set.
std::size_t simd_lanes
Which number of SIMD lanes to prefer.
option_configs::IndexScanCompilationStrategy index_scan_compilation_strategy
Which compilation strategy should be used for wasm::IndexScan.
std::size_t get_num_simd_lanes(const DataLayout &layout, const Schema &layout_schema, const Schema &tuple_schema)
Returns the number of SIMD lanes used for accessing tuples of schema tuple_schema in SIMDfied manner ...
Definition: DataLayout.cpp:244
const Schema & layout_schema
Definition: DataLayout.hpp:255
_I32x1 strcmp(NChar left, NChar right, bool reverse=false)
Compares two strings left and right.
Definition: WasmUtil.cpp:3270
_I32x1 strncmp(NChar left, NChar right, U32x1 len, bool reverse=false)
Compares two strings left and right.
Definition: WasmUtil.cpp:3102
To ToL to()
Definition: WasmDSL.hpp:1986
bool can_be_null(const SQL_t &variant)
Definition: WasmUtil.hpp:452
Bool< L > not_null(SQL_t &variant)
Definition: WasmUtil.hpp:475
typename detail::_var_helper< T >::type _Var
Local variable that can always be NULL.
Definition: WasmDSL.hpp:5785
typename detail::var_helper< T >::type Var
Local variable.
Definition: WasmDSL.hpp:5780
auto make_signed()
Conversion of a PrimitiveExpr<T, L> to a PrimitiveExpr<std::make_signed_t<T>, L>.
Definition: WasmDSL.hpp:3651
void GOTO(const Block &block)
Jumps to the end of block.
Definition: WasmDSL.hpp:6200
void compile_load_point_access(const Schema &tuple_value_schema, const Schema &tuple_addr_schema, Ptr< void > base_address, const storage::DataLayout &layout, const Schema &layout_schema, U32x1 tuple_id)
Compiles the data layout layout starting at memory address base_address and containing tuples of sche...
Definition: WasmUtil.cpp:2480
and
Constructs a new PrimitiveExpr from a constant value.
Definition: WasmDSL.hpp:1520
std::size_t L
Definition: WasmDSL.hpp:528
typename detail::global_helper< T >::type Global
Global variable.
Definition: WasmDSL.hpp:5790
Bool< L > value
Definition: WasmUtil.hpp:1317
Bool< L > is_null(SQL_t &variant)
Definition: WasmUtil.hpp:461
auto Select(C &&_cond, T &&_tru, U &&_fals)
Definition: WasmDSL.hpp:6216
std::tuple< Block, Block, Block > compile_load_sequential(const Schema &tuple_value_schema, const Schema &tuple_addr_schema, Ptr< void > base_address, const storage::DataLayout &layout, std::size_t num_simd_lanes, const Schema &layout_schema, Variable< uint32_t, Kind, false > &tuple_id)
Compiles the data layout layout containing tuples of schema layout_schema such that it sequentially l...
auto max(PrimitiveExpr< U, L > other) -> PrimitiveExpr< common_type_t< T, U >, L > std
Computes the maximum of this and other.
Definition: WasmDSL.hpp:2505
Bool< L > uint8_t n
Definition: WasmUtil.hpp:1318
void discard()
Discards this.
Definition: WasmDSL.hpp:1589
PrimitiveExpr< uint64_t, L > L L L L U
Definition: WasmDSL.hpp:2353
for(std::size_t idx=1;idx< num_vectors;++idx) res.emplace((vectors_[idx].bitmask()<< uint32_t(idx *vector_type return * res
Definition: WasmDSL.hpp:3697
auto op
Definition: WasmDSL.hpp:2385
std::size_t bool
Definition: WasmDSL.hpp:528
PrimitiveExpr< ResultType, ResultL > binary(::wasm::BinaryOp op, PrimitiveExpr< OperandType, OperandL > other)
Helper function to implement binary operations.
Definition: WasmDSL.hpp:1618
PrimitiveExpr clone() const
Creates and returns a deep copy of this.
Definition: WasmDSL.hpp:1578
and arithmetically_combinable< T, U, L > auto L auto L auto min(PrimitiveExpr< U, L > other) -> PrimitiveExpr< common_type_t< T, U >, L >
Definition: WasmDSL.hpp:2475
static constexpr std::size_t num_simd_lanes
‍the number of SIMD lanes of the represented expression, i.e. 1 for scalar and at least 2 for vectori...
Definition: WasmDSL.hpp:1467
‍mutable namespace
Definition: Backend.hpp:10
std::function< void(void)> pipeline_t
bool streq(const char *first, const char *second)
Definition: fn.hpp:29
bool strneq(const char *first, const char *second, std::size_t n)
Definition: fn.hpp:30
bool M_EXPORT contains(const H &haystack, const N &needle)
Checks whether haystack contains needle.
Definition: fn.hpp:383
T(x)
and
Definition: enum_ops.hpp:12
and arithmetic< U > and same_signedness< T, U > U
Definition: concepts.hpp:90
bool M_EXPORT init(void)
Initializes the mu*t*able library.
Definition: mutable.cpp:23
auto visit(Callable &&callable, Base &obj, m::tag< Callable > &&=m::tag< Callable >())
Generic implementation to visit a class hierarchy, with similar syntax as std::visit.
Definition: Visitor.hpp:138
void register_wasm_operators(PhysicalOptimizer &phys_opt)
Registers physical Wasm operators in phys_opt depending on the set CLI options.
STL namespace.
‍helper struct for aggregates
Schema::entry_type entry
aggregate entry consisting of identifier, type, and constraints
const std::vector< std::unique_ptr< ast::Expr > > & args
aggregate arguments
m::Function::fnid_t fnid
aggregate function
‍helper struct for AVG aggregates
Schema::Identifier running_count
identifier of running count
Schema::Identifier sum
potential identifier for sum (only set if AVG is computed once at the end)
bool compute_running_avg
flag whether running AVG must be computed instead of one computation at the end
‍helper struct holding the bounds for index scan
bool is_inclusive_hi
flag to indicate if bounds are inclusive
Schema::entry_type attribute
Attribute for which bounds should hold.
std::optional< std::reference_wrapper< const ast::Expr > > hi
lo and hi bounds
std::optional< std::reference_wrapper< const ast::Expr > > lo
A block of size N contains N tuples.
Definition: Interpreter.hpp:20
The boolean type.
Definition: Type.hpp:230
The catalog contains all Databases and keeps track of all meta information of the database system.
Definition: Catalog.hpp:215
Database & get_database_in_use()
Returns a reference to the Database that is currently in use, if any.
Definition: Catalog.hpp:295
ThreadSafePooledString pool(const char *str) const
Creates an internalized copy of the string str by adding it to the internal StringPool.
Definition: Catalog.hpp:274
static Catalog & Get()
Return a reference to the single Catalog instance.
m::ArgParser & arg_parser()
Definition: Catalog.hpp:253
The type of character strings, both fixed length and varying length.
Definition: Type.hpp:290
auto find(const Schema::Identifier &id) const
Definition: Condition.hpp:61
void merge(ConditionPropertyMap &other)
Definition: Condition.hpp:48
void add(Schema::Identifier id, Property P)
Definition: Condition.hpp:36
Cond & get_condition()
Definition: Condition.hpp:305
static ConditionSet Make_Unsatisfiable()
Definition: Condition.hpp:323
void add_condition(Cond &&cond)
Definition: Condition.hpp:289
void project_and_rename(const std::vector< std::pair< Schema::Identifier, Schema::Identifier > > &old2new)
Definition: Condition.cpp:19
void add_or_replace_condition(Cond &&cond)
Definition: Condition.hpp:298
ThreadSafePooledString name
the name of the database
Definition: Schema.hpp:892
The date type.
Definition: Type.hpp:364
The date type.
Definition: Type.hpp:335
virtual void execute(setup_t setup, pipeline_t pipeline, teardown_t teardown) const =0
Executes this physical operator match.
virtual void print(std::ostream &out, unsigned level=0) const =0
The numeric type represents integer and floating-point types of different precision and scale.
Definition: Type.hpp:393
double estimated_cardinality
‍the estimated cardinality of the result set of this Operator
Definition: Operator.hpp:33
An Operator represents an operation in a query plan.
Definition: Operator.hpp:45
OperatorInformation & info()
Definition: Operator.hpp:72
bool has_info() const
Definition: Operator.hpp:71
The physical optimizer interface.
void register_operator()
Registers a new physical operator which then may be used to find a covering.
A data type representing a pooled (or internalized) object.
Definition: Pool.hpp:168
An Identifier is composed of a name and an optional prefix.
Definition: Schema.hpp:42
ThreadSafePooledString name
the name of this Identifier
Definition: Schema.hpp:48
static entry_type CreateArtificial()
Definition: Schema.hpp:100
@ NOT_NULLABLE
entry must not be NULL
Definition: Schema.hpp:80
A Schema represents a sequence of identifiers, optionally with a prefix, and their associated types.
Definition: Schema.hpp:39
std::size_t num_entries() const
Returns the number of entries in this Schema.
Definition: Schema.hpp:124
Schema deduplicate() const
Returns a deduplicated version of this Schema, i.e.
Definition: Schema.hpp:190
iterator find(const Identifier &id)
Returns an iterator to the entry with the given Identifier id, or end() if no such entry exists.
Definition: Schema.hpp:129
void add(entry_type e)
Adds the entry e to this Schema.
Definition: Schema.hpp:181
Schema drop_constants() const
Returns a copy of this Schema where all constant entries are removed.
Definition: Schema.hpp:200
bool has(const Identifier &id) const
Returns true iff this Schema contains an entry with Identifier id.
Definition: Schema.hpp:140
order_t & orders()
Definition: Condition.hpp:168
This class represents types in the SQL type system.
Definition: Type.hpp:46
static Pooled< Numeric > Get_Double(category_t category)
Returns a Numeric type of given category for 64 bit floating-points.
Definition: Type.cpp:104
static Pooled< Numeric > Get_Integer(category_t category, unsigned num_bytes)
Returns a Numeric type for integrals of given category and num_bytes bytes.
Definition: Type.cpp:94
static WasmContext & Get_Wasm_Context_By_ID(unsigned id)
Returns a reference to the WasmContext with ID id.
A binary expression.
Definition: AST.hpp:348
A constant: a string literal or a numeric constant.
Definition: AST.hpp:213
A designator.
Definition: AST.hpp:134
The error expression.
Definition: AST.hpp:116
An expression.
Definition: AST.hpp:39
const Type * type() const
Returns the Type of this Expr.
Definition: AST.hpp:58
virtual bool can_be_null() const =0
Returns true iff this Expr is nullable, i.e.
A function application.
Definition: AST.hpp:246
A query expression for nested queries.
Definition: AST.hpp:389
static Token CreateArtificial(TokenType type=TK_EOF)
Definition: Token.hpp:29
A CNF represents a conjunction of cnf::Clauses.
Definition: CNF.hpp:134
Schema get_required() const
Returns a Schema instance containing all required definitions (of Attributes and other Designators).
Definition: CNF.hpp:138
A cnf::Clause represents a disjunction of Predicates.
Definition: CNF.hpp:87
A Predicate contains a Expr of Boolean type in either positive or negative form.
Definition: CNF.hpp:16
A simple index based on a sorted array that maps keys to their tuple_id.
Definition: Index.hpp:58
A recursive model index with two layers consiting only of linear monels that maps keys to their tuple...
Definition: Index.hpp:163
Signals that an argument to a function of method was invalid.
Definition: exception.hpp:37
static setup_t Make_Without_Parent(base_t &&callback=base_t())
This is an interface for factories that compute particular DataLayouts for a given sequence of Types,...
virtual std::unique_ptr< DataLayoutFactory > clone() const =0
Creates and returns a deep copy of this.
Definition: tag.hpp:8
static teardown_t Make_Without_Parent(base_t &&callback=base_t())
Exception class which can be thrown to skip recursion of the subtree in pre-order visitors.
Definition: Visitor.hpp:18
Exception class which can be thrown to stop entire recursion in visitors.
Definition: Visitor.hpp:16
static ConditionSet post_condition(const Match< Aggregation > &M)
static ConditionSet pre_condition(std::size_t child_idx, const std::tuple< const AggregationOperator * > &partial_inner_nodes)
static void execute(const Match< Aggregation > &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
std::variant< var_t_< IsGlobal, I64< L > >, std::pair< var_t_< IsGlobal, I8< L > >, var_t_< IsGlobal, Bool< L > > >, std::pair< var_t_< IsGlobal, I16< L > >, var_t_< IsGlobal, Bool< L > > >, std::pair< var_t_< IsGlobal, I32< L > >, var_t_< IsGlobal, Bool< L > > >, std::pair< var_t_< IsGlobal, I64< L > >, var_t_< IsGlobal, Bool< L > > >, std::pair< var_t_< IsGlobal, Float< L > >, var_t_< IsGlobal, Bool< L > > >, std::pair< var_t_< IsGlobal, Double< L > >, var_t_< IsGlobal, Bool< L > > > > agg_t_
Represents a code block, i.e.
Definition: WasmDSL.hpp:1006
void attach_to_current()
Attaches this Block to the wasm::Block currently active in the Module.
Definition: WasmDSL.hpp:1085
Buffers tuples by materializing them into memory.
Definition: WasmUtil.hpp:1070
void resume_pipeline(param_t tuple_value_schema=param_t(), param_t tuple_addr_schema=param_t()) const
Emits code into a separate function to resume the pipeline for each value tuple of schema tuple_value...
Definition: WasmUtil.cpp:2679
Ptr< void > base_address() const
Returns the base address of the buffer.
Definition: WasmUtil.hpp:1110
void consume()
Emits code to store the current tuple into the buffer.
Definition: WasmUtil.cpp:2910
void setup()
Performs the setup of all local variables of this buffer (by reading them from the global backups iff...
Definition: WasmUtil.cpp:2583
void teardown()
Performs the teardown of all local variables of this buffer (by storing them into the global backups ...
Definition: WasmUtil.cpp:2642
U32x1 size() const
Returns the current size of the buffer.
Definition: WasmUtil.hpp:1119
static ConditionSet pre_condition(std::size_t child_idx, const std::tuple< const CallbackOperator * > &partial_inner_nodes)
static void execute(const Match< Callback > &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
uint32_t get_literal_raw_address(const char *literal) const
Returns the raw address at which literal is stored.
Definition: WasmUtil.hpp:926
void inc_num_tuples(U32x1 n=U32x1(1))
Increments the number of result tuples produced by n.
Definition: WasmUtil.hpp:914
U32x1 num_tuples() const
Returns the number of result tuples produced.
Definition: WasmUtil.hpp:910
std::size_t num_simd_lanes() const
Returns the number of SIMD lanes used.
Definition: WasmUtil.hpp:939
NChar get_literal_address(const char *literal) const
Returns the address at which literal is stored.
Definition: WasmUtil.hpp:932
std::size_t num_simd_lanes_preferred() const
Returns the number of SIMD lanes preferred by other operators.
Definition: WasmUtil.hpp:944
Environment & env()
Returns the current Environment.
Definition: WasmUtil.hpp:905
void set_num_simd_lanes(std::size_t n)
Sets the number of SIMD lanes used to n.
Definition: WasmUtil.hpp:941
void update_num_simd_lanes_preferred(std::size_t n)
Updates the number of SIMD lanes preferred by n.
Definition: WasmUtil.hpp:946
void set_num_tuples(U32x1 n)
Set the number of result tuples produced to n.
Definition: WasmUtil.hpp:912
static CodeGenContext & Get()
Definition: WasmUtil.hpp:889
Scope scoped_environment()
Creates a new, scoped Environment.
Definition: WasmUtil.hpp:897
Binds Schema::Identifiers to Expr<T>s.
Definition: WasmUtil.hpp:563
auto compile(T &&t) const
‍Compile t by delegating compilation to an ExprCompiler for this Environment.
Definition: WasmUtil.hpp:742
void add_predicate(SQL_boolean_t &&pred)
‍Adds the predicate pred to the predication predicate.
Definition: WasmUtil.hpp:759
void add(Schema::Identifier id, T &&expr)
‍Adds a mapping from id to expr.
Definition: WasmUtil.hpp:619
SQL_t get(const Schema::Identifier &id) const
‍Returns the copied entry for identifier id.
Definition: WasmUtil.hpp:699
SQL_boolean_t extract_predicate()
‍Returns the moved current predication predicate.
Definition: WasmUtil.hpp:779
bool has(const Schema::Identifier &id) const
Returns true iff this Environment contains id.
Definition: WasmUtil.hpp:587
static ConditionSet pre_condition(std::size_t child_idx, const std::tuple< const FilterOperator * > &partial_inner_nodes)
static ConditionSet adapt_post_condition(const Match< Filter > &M, const ConditionSet &post_cond_child)
static double cost(const Match< Filter > &)
static void execute(const Match< Filter > &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
A handle to create a Function and to create invocations of that function.
Definition: WasmDSL.hpp:1368
static ConditionSet pre_condition(std::size_t child_idx, const std::tuple< const GroupingOperator *, const JoinOperator *, const Wildcard *, const Wildcard * > &partial_inner_nodes)
static double cost(const Match< HashBasedGroupJoin > &)
static ConditionSet post_condition(const Match< HashBasedGroupJoin > &M)
static void execute(const Match< HashBasedGroupJoin > &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
static double cost(const Match< HashBasedGrouping > &)
static ConditionSet post_condition(const Match< HashBasedGrouping > &M)
static ConditionSet pre_condition(std::size_t child_idx, const std::tuple< const GroupingOperator * > &partial_inner_nodes)
static void execute(const Match< HashBasedGrouping > &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
Helper struct as proxy to access a hash table entry.
Definition: WasmAlgo.hpp:369
value_t get(const Schema::Identifier &id) const
‍Returns the copied entry for identifier id.
Definition: WasmAlgo.hpp:462
value_t extract(const Schema::Identifier &id)
‍Returns the moved entry for identifier id.
Definition: WasmAlgo.hpp:443
Helper struct as proxy to access a single value (inclusive NULL bit) of a hash table entry.
Definition: WasmAlgo.hpp:66
static double cost(const Match< IndexScan > &M)
static void execute(const Match< IndexScan > &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
static ConditionSet pre_condition(std::size_t child_idx, const std::tuple< const FilterOperator *, const ScanOperator * > &partial_inner_nodes)
static ConditionSet post_condition(const Match< IndexScan > &M)
static ConditionSet pre_condition(std::size_t child_idx, const std::tuple< const FilterOperator * > &partial_inner_nodes)
static double cost(const Match< LazyDisjunctiveFilter > &M)
static void execute(const Match< LazyDisjunctiveFilter > &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
static void execute(const Match< Limit > &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
static ConditionSet pre_condition(std::size_t child_idx, const std::tuple< const LimitOperator * > &partial_inner_nodes)
Linear probing strategy, i.e.
Definition: WasmAlgo.hpp:1018
An abstract MatchBase for the WasmV8 backend.
PrimitiveExpr< T, L > get_global(const char *name)
Definition: WasmDSL.hpp:6710
static Module & Get()
Definition: WasmDSL.hpp:715
friend struct Allocator
Definition: WasmDSL.hpp:653
static unsigned ID()
Returns the ID of the current module.
Definition: WasmDSL.hpp:722
void emit_call(const char *fn, PrimitiveExpr< ParamTypes, ParamLs >... args)
Definition: WasmDSL.hpp:6717
static void execute(const Match< NestedLoopsJoin > &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
static ConditionSet pre_condition(std::size_t child_idx, const std::tuple< const JoinOperator * > &partial_inner_nodes)
static double cost(const Match< NestedLoopsJoin > &M)
static ConditionSet adapt_post_conditions(const Match< NestedLoopsJoin > &M, std::vector< std::reference_wrapper< const ConditionSet > > &&post_cond_children)
static void execute(const Match< NoOpSorting > &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
static ConditionSet pre_condition(std::size_t child_idx, const std::tuple< const SortingOperator * > &partial_inner_nodes)
static void execute(const Match< NoOp > &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
std::variant< var_t_< IsGlobal, I64x1 >, std::pair< var_t_< IsGlobal, I8x1 >, std::optional< var_t_< IsGlobal, Boolx1 > > >, std::pair< var_t_< IsGlobal, I16x1 >, std::optional< var_t_< IsGlobal, Boolx1 > > >, std::pair< var_t_< IsGlobal, I32x1 >, std::optional< var_t_< IsGlobal, Boolx1 > > >, std::pair< var_t_< IsGlobal, I64x1 >, std::optional< var_t_< IsGlobal, Boolx1 > > >, std::pair< var_t_< IsGlobal, Floatx1 >, std::optional< var_t_< IsGlobal, Boolx1 > > >, std::pair< var_t_< IsGlobal, Doublex1 >, std::optional< var_t_< IsGlobal, Boolx1 > > > > agg_t_
static ConditionSet adapt_post_condition(const Match< OrderedGrouping > &M, const ConditionSet &post_cond_child)
static ConditionSet pre_condition(std::size_t child_idx, const std::tuple< const GroupingOperator * > &partial_inner_nodes)
std::variant< var_t_< IsGlobal, Ptr< Charx1 > >, std::pair< var_t_< IsGlobal, Boolx1 >, std::optional< var_t_< IsGlobal, Boolx1 > > >, std::pair< var_t_< IsGlobal, I8x1 >, std::optional< var_t_< IsGlobal, Boolx1 > > >, std::pair< var_t_< IsGlobal, I16x1 >, std::optional< var_t_< IsGlobal, Boolx1 > > >, std::pair< var_t_< IsGlobal, I32x1 >, std::optional< var_t_< IsGlobal, Boolx1 > > >, std::pair< var_t_< IsGlobal, I64x1 >, std::optional< var_t_< IsGlobal, Boolx1 > > >, std::pair< var_t_< IsGlobal, Floatx1 >, std::optional< var_t_< IsGlobal, Boolx1 > > >, std::pair< var_t_< IsGlobal, Doublex1 >, std::optional< var_t_< IsGlobal, Boolx1 > > > > key_t_
static double cost(const Match< OrderedGrouping > &)
static void execute(const Match< OrderedGrouping > &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
static ConditionSet pre_condition(std::size_t child_idx, const std::tuple< const PrintOperator * > &partial_inner_nodes)
static void execute(const Match< Print > &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
static ConditionSet pre_condition(std::size_t child_idx, const std::tuple< const ProjectionOperator * > &partial_inner_nodes)
static void execute(const Match< Projection > &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
static ConditionSet adapt_post_condition(const Match< Projection > &M, const ConditionSet &post_cond_child)
Quadratic probing strategy, i.e.
Definition: WasmAlgo.hpp:1028
static ConditionSet pre_condition(std::size_t child_idx, const std::tuple< const SortingOperator * > &partial_inner_nodes)
static void execute(const Match< Quicksort > &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
static ConditionSet post_condition(const Match< Quicksort > &M)
static ConditionSet post_condition(const Match< Scan > &M)
static ConditionSet pre_condition(std::size_t child_idx, const std::tuple< const ScanOperator * > &partial_inner_nodes)
static void execute(const Match< Scan > &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
static double cost(const Match< SimpleHashJoin > &M)
static ConditionSet adapt_post_conditions(const Match< SimpleHashJoin > &M, std::vector< std::reference_wrapper< const ConditionSet > > &&post_cond_children)
static void execute(const Match< SimpleHashJoin > &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
static ConditionSet pre_condition(std::size_t child_idx, const std::tuple< const JoinOperator *, const Wildcard *, const Wildcard * > &partial_inner_nodes)
static ConditionSet pre_condition(std::size_t child_idx, const std::tuple< const JoinOperator *, const Wildcard *, const Wildcard * > &partial_inner_nodes)
static double cost(const Match< SortMergeJoin > &M)
static ConditionSet adapt_post_conditions(const Match< SortMergeJoin > &M, std::vector< std::reference_wrapper< const ConditionSet > > &&post_cond_children)
static void execute(const Match< SortMergeJoin > &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
void operator()(Const< MatchBase > &)
typename super::template Const< T > Const
typename super::template Const< T > Const
void operator()(Const< MatchBase > &)
A generic base class for implementing recursive wasm::MatchBase visitors.
Helper type to deduce the Expr<U> type given a.
Definition: WasmDSL.hpp:160
const Operator & op
friend std::ostream & operator<<(std::ostream &out, const print_info &info)