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
980uint32_t compute_initial_ht_capacity(const Operator &op, double load_factor) {
981 uint64_t initial_capacity;
982 if (options::hash_table_initial_capacity) {
983 initial_capacity = *options::hash_table_initial_capacity;
984 } else {
985 if (op.has_info())
986 initial_capacity = static_cast<uint64_t>(std::ceil(op.info().estimated_cardinality / load_factor));
987 else if (auto scan = cast<const ScanOperator>(&op))
988 initial_capacity = static_cast<uint64_t>(std::ceil(scan->store().num_rows() / load_factor));
989 else
990 initial_capacity = 1024; // fallback
991 }
992 return std::in_range<uint32_t>(initial_capacity) ? initial_capacity : std::numeric_limits<uint32_t>::max();
993}
994
997{
999 std::optional<std::reference_wrapper<const ast::Expr>> lo, hi;
1001};
1002
1005 if (is<const Constant>(expr))
1006 return true;
1007 if (auto u = cast<const UnaryExpr>(&expr)) {
1008 /* `UnaryExpr` must be followed by `Constant`. */
1009 if ((u->op().type == TK_MINUS or u->op().type == TK_PLUS) and is<const Constant>(*u->expr))
1010 return true;
1011 }
1012 return false;
1013}
1014
1017std::pair<const Constant&, bool> get_valid_bound(const ast::Expr &expr) {
1018 M_insist(is_valid_bound(expr), "bound must be valid");
1019 if (auto c = cast<const Constant>(&expr))
1020 return { *c, false };
1021 auto &u = as<const UnaryExpr>(expr);
1022 return { as<const Constant>(*u.expr), u.op().type == TK_MINUS };
1023}
1024
1029{
1030 index_scan_bounds_t bounds;
1031
1032 M_insist(not cnf.empty(), "filter condition must not be empty");
1033 auto designators = cnf.get_required();
1034 M_insist(designators.num_entries() == 1, "filter condition must contain exactly one designator");
1035 bounds.attribute = designators[0];
1036
1037 for (auto &clause : cnf) {
1038 M_insist(clause.size() == 1, "invalid predicate");
1039 auto &literal = clause[0];
1040 auto &binary = as<const BinaryExpr>(literal.expr());
1041 M_insist((is<const Designator>(binary.lhs) and is_valid_bound(*binary.rhs)) or
1042 (is<const Designator>(binary.rhs) and is_valid_bound(*binary.lhs)), "invalid predicate");
1043 bool has_attribute_left = is<const Designator>(binary.lhs);
1044 auto &bound = has_attribute_left ? *binary.rhs : *binary.lhs;
1045
1046 switch(binary.tok.type) {
1047 default:
1048 M_unreachable("unsupported token type");
1049 case TK_EQUAL:
1050 M_insist(not bool(bounds.lo) and not bool(bounds.hi), "bound already set");
1051 bounds.lo = bounds.hi = std::cref(bound);
1052 bounds.is_inclusive_lo = bounds.is_inclusive_hi = true;
1053 break;
1054 case TK_GREATER:
1055 if (has_attribute_left) {
1056 M_insist(not bool(bounds.lo), "lo bound already set");
1057 bounds.lo = std::cref(bound);
1058 bounds.is_inclusive_lo = false;
1059 } else {
1060 M_insist(not bool(bounds.hi), "hi bound already set");
1061 bounds.hi = std::cref(bound);
1062 bounds.is_inclusive_hi = false;
1063 }
1064 break;
1065 case TK_GREATER_EQUAL:
1066 if (has_attribute_left) {
1067 M_insist(not bool(bounds.lo), "lo bound already set");
1068 bounds.lo = std::cref(bound);
1069 bounds.is_inclusive_lo = true;
1070 } else {
1071 M_insist(not bool(bounds.hi), "hi bound already set");
1072 bounds.hi = std::cref(bound);
1073 bounds.is_inclusive_hi = true;
1074 }
1075 break;
1076 case TK_LESS:
1077 if (has_attribute_left) {
1078 M_insist(not bool(bounds.hi), "hi bound already set");
1079 bounds.hi = std::cref(bound);
1080 bounds.is_inclusive_hi = false;
1081 } else {
1082 M_insist(not bool(bounds.lo), "lo bound already set");
1083 bounds.lo = std::cref(bound);
1084 bounds.is_inclusive_lo = false;
1085 }
1086 break;
1087 case TK_LESS_EQUAL:
1088 if (has_attribute_left) {
1089 M_insist(not bool(bounds.hi), "hi bound already set");
1090 bounds.hi = std::cref(bound);
1091 bounds.is_inclusive_hi = true;
1092 } else {
1093 M_insist(not bool(bounds.lo), "lo bound already set");
1094 bounds.lo = std::cref(bound);
1095 bounds.is_inclusive_lo = true;
1096 }
1097 break;
1098 }
1099 }
1100 M_insist(bool(bounds.lo) or bool(bounds.hi), "either bound must be set");
1101 return bounds;
1102}
1103
1104
1105/*======================================================================================================================
1106 * NoOp
1107 *====================================================================================================================*/
1108
1110{
1111 std::optional<Var<U32x1>> num_tuples;
1112
1113 M.child->execute(
1114 /* setup= */ setup_t::Make_Without_Parent([&](){ num_tuples.emplace(CodeGenContext::Get().num_tuples()); }),
1115 /* pipeline= */ [&](){
1116 M_insist(bool(num_tuples));
1117 if (auto &env = CodeGenContext::Get().env(); env.predicated()) {
1118 switch (CodeGenContext::Get().num_simd_lanes()) {
1119 default: M_unreachable("invalid number of simd lanes");
1120 case 1: {
1121 *num_tuples += env.extract_predicate<_Boolx1>().is_true_and_not_null().to<uint32_t>();
1122 break;
1123 }
1124 case 16: {
1125 auto pred = env.extract_predicate<_Boolx16>().is_true_and_not_null();
1126 *num_tuples += pred.bitmask().popcnt();
1127 break;
1128 }
1129 }
1130 } else {
1131 *num_tuples += uint32_t(CodeGenContext::Get().num_simd_lanes());
1132 }
1133 },
1134 /* teardown= */ teardown_t::Make_Without_Parent([&](){
1135 M_insist(bool(num_tuples));
1136 CodeGenContext::Get().set_num_tuples(*num_tuples);
1137 num_tuples.reset();
1138 })
1139 );
1140}
1141
1142
1143/*======================================================================================================================
1144 * Callback
1145 *====================================================================================================================*/
1146
1147template<bool SIMDfied>
1148ConditionSet Callback<SIMDfied>::pre_condition(std::size_t child_idx, const std::tuple<const CallbackOperator*>&)
1149{
1150 M_insist(child_idx == 0);
1151
1152 ConditionSet pre_cond;
1153
1154 if constexpr (SIMDfied) {
1155 /*----- SIMDfied callback supports SIMD but not predication. -----*/
1156 pre_cond.add_condition(Predicated(false));
1157 } else {
1158 /*----- Non-SIMDfied callback does not support SIMD. -----*/
1159 pre_cond.add_condition(NoSIMD());
1160 }
1161
1162 return pre_cond;
1163}
1164
1165template<bool SIMDfied>
1167{
1168 M_insist(bool(M.result_set_factory), "`wasm::Callback` must have a factory for the result set");
1169
1170 auto result_set_schema = M.callback.schema().drop_constants().deduplicate();
1171 write_result_set(result_set_schema, *M.result_set_factory, M.result_set_window_size, *M.child);
1172}
1173
1174
1175/*======================================================================================================================
1176 * Print
1177 *====================================================================================================================*/
1178
1179template<bool SIMDfied>
1180ConditionSet Print<SIMDfied>::pre_condition(std::size_t child_idx, const std::tuple<const PrintOperator*>&)
1181{
1182 M_insist(child_idx == 0);
1183
1184 ConditionSet pre_cond;
1185
1186 if constexpr (SIMDfied) {
1187 /*----- SIMDfied print supports SIMD but not predication. -----*/
1188 pre_cond.add_condition(Predicated(false));
1189 } else {
1190 /*----- Non-SIMDfied print does not support SIMD. -----*/
1191 pre_cond.add_condition(NoSIMD());
1192 }
1193
1194 return pre_cond;
1195}
1196
1197template<bool SIMDfied>
1199{
1200 M_insist(bool(M.result_set_factory), "`wasm::Print` must have a factory for the result set");
1201
1202 auto result_set_schema = M.print_op.schema().drop_constants().deduplicate();
1203 write_result_set(result_set_schema, *M.result_set_factory, M.result_set_window_size, *M.child);
1204}
1205
1206
1207/*======================================================================================================================
1208 * Scan
1209 *====================================================================================================================*/
1210
1211template<bool SIMDfied>
1213 const std::tuple<const ScanOperator*> &partial_inner_nodes)
1214{
1215 M_insist(child_idx == 0);
1216
1217 ConditionSet pre_cond;
1218
1219 if constexpr (SIMDfied) {
1220 auto &scan = *std::get<0>(partial_inner_nodes);
1221 auto &table = scan.store().table();
1222
1223 /*----- SIMDfied scan needs the data layout to support SIMD. -----*/
1224 if (not supports_simd(table.layout(), table.schema(scan.alias()), scan.schema()))
1226
1227 /*----- SIMDfied scan needs the number of rows to load be a whole multiple of the number of SIMD lanes used. -*/
1228 if (scan.store().num_rows() % get_num_simd_lanes(table.layout(), table.schema(scan.alias()), scan.schema()) != 0)
1230 }
1231
1232 return pre_cond;
1233}
1234
1235template<bool SIMDfied>
1237{
1238 ConditionSet post_cond;
1239
1240 /*----- Scan does not introduce predication. -----*/
1241 post_cond.add_condition(Predicated(false));
1242
1243 if constexpr (SIMDfied) {
1244 /*----- SIMDfied scan introduces SIMD vectors with respective number of lanes. -----*/
1245 auto &table = M.scan.store().table();
1246 const auto num_simd_lanes = get_num_simd_lanes(table.layout(), table.schema(M.scan.alias()), M.scan.schema());
1247 post_cond.add_condition(SIMD(num_simd_lanes));
1248 } else {
1249 /*----- Non-SIMDfied scan does not introduce SIMD. -----*/
1250 post_cond.add_condition(NoSIMD());
1251 }
1252
1253 /*----- Check if any attribute of scanned table is assumed to be sorted. -----*/
1254 Sortedness::order_t orders;
1255 for (auto &e : M.scan.schema()) {
1256 auto pred = [&e](const auto &p){ return e.id == p.first; };
1257 if (auto it = std::find_if(options::sorted_attributes.cbegin(), options::sorted_attributes.cend(), pred);
1258 it != options::sorted_attributes.cend())
1259 {
1260 orders.add(e.id, it->second ? Sortedness::O_ASC : Sortedness::O_DESC);
1261 }
1262 }
1263 if (not orders.empty())
1264 post_cond.add_condition(Sortedness(std::move(orders)));
1265
1266 return post_cond;
1267}
1268
1269template<bool SIMDfied>
1270void Scan<SIMDfied>::execute(const Match<Scan> &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
1271{
1272 auto &schema = M.scan.schema();
1273 auto &table = M.scan.store().table();
1274
1275 M_insist(schema == schema.drop_constants().deduplicate(), "schema of `ScanOperator` must not contain NULL or duplicates");
1276 M_insist(not table.layout().is_finite(), "layout for `wasm::Scan` must be infinite");
1277
1278 Var<U32x1> tuple_id; // default initialized to 0
1279
1280 /*----- Compute possible number of SIMD lanes and decide which to use with regard to other operators preferences. */
1281 const auto layout_schema = table.schema(M.scan.alias());
1283 const auto num_simd_lanes_preferred =
1284 CodeGenContext::Get().num_simd_lanes_preferred(); // get other operators preferences
1285 const std::size_t num_simd_lanes =
1286 SIMDfied ? (options::double_pumping ? 2 : 1)
1287 * std::max(num_simd_lanes_preferred, get_num_simd_lanes(table.layout(), layout_schema, schema))
1288 : 1;
1290
1291 /*----- Import the number of rows of `table`. -----*/
1292 U32x1 num_rows = get_num_rows(table.name());
1293
1294 /*----- If no attributes must be loaded, generate a loop just executing the pipeline `num_rows`-times. -----*/
1295 if (schema.num_entries() == 0) {
1296 setup();
1297 WHILE (tuple_id < num_rows) {
1298 tuple_id += uint32_t(num_simd_lanes);
1299 pipeline();
1300 }
1301 teardown();
1302 return;
1303 }
1304
1305 /*----- Import the base address of the mapped memory. -----*/
1306 Ptr<void> base_address = get_base_address(table.name());
1307
1308 /*----- Emit setup code *before* compiling data layout to not overwrite its temporary boolean variables. -----*/
1309 setup();
1310
1311 /*----- Compile data layout to generate sequential load from table. -----*/
1312 static Schema empty_schema;
1313 auto [inits, loads, jumps] = compile_load_sequential(schema, empty_schema, base_address, table.layout(),
1314 num_simd_lanes, layout_schema, tuple_id);
1315
1316 /*----- Generate the loop for the actual scan, with the pipeline emitted into the loop body. -----*/
1317 inits.attach_to_current();
1318 WHILE (tuple_id < num_rows) {
1319 loads.attach_to_current();
1320 pipeline();
1321 jumps.attach_to_current();
1322 }
1323
1324 /*----- Emit teardown code. -----*/
1325 teardown();
1326}
1327
1328
1329/*======================================================================================================================
1330 * Index Scan
1331 *====================================================================================================================*/
1332
1333template<idx::IndexMethod IndexMethod>
1335 const std::tuple<const FilterOperator*,
1336 const ScanOperator*> &partial_inner_nodes)
1337{
1338 M_insist(child_idx == 0);
1339
1340 /*----- Check if index on one of the attributes in filter condition exists. -----*/
1341 auto &filter = *std::get<0>(partial_inner_nodes);
1342 auto &cnf = filter.filter();
1343
1344 M_insist(not cnf.empty(), "Filter condition must not be empty");
1345
1346 auto &scan = *std::get<1>(partial_inner_nodes);
1347 auto &table = scan.store().table();
1348
1349 Catalog &C = Catalog::Get();
1350 auto &DB = C.get_database_in_use();
1351
1352 /* Extract attributes from filter condition. */
1353 std::vector<Schema::Identifier> ids;
1354 for (auto &entry : cnf.get_required()) {
1355 Schema::Identifier &id = entry.id;
1356 M_insist(table.name() == id.prefix, "Table name should match designator table name");
1357
1358 /* Keep attributes for which an index exists. */
1359 if (DB.has_index(table.name(), id.name, IndexMethod))
1360 ids.push_back(id);
1361 }
1362 if (ids.empty()) // no usable index found
1364
1365 /*----- Check if filter condition is supported. -----*/
1366 /* We currently only support filter conditions of the following form.
1367 * 1. Point: condition with a single equality predicate, e.g.
1368 * x = 42.
1369 * 2. One-sided range: condition with a single greater/greater-or-equal/less/less-or-equal predicate, e.g.
1370 * x > 42.
1371 * 3. Two-sided range: condition with a greater/greater-or-equal predicate and a less/less-or-equal predicate, e.g.
1372 * x > 42 AND x <= 89.
1373 * Attributes may appear on either side. The other side is required to be a `Constant`. */
1374 if (ids.size() > 1) // conditions with more than one attribute currently not supported
1376
1377 bool has_lo_bound = false;
1378 bool has_hi_bound = false;
1379 for (auto &clause : cnf) {
1380 if (clause.size() != 1) // disjunctions not supported
1382
1383 auto &predicate = clause[0];
1384 if (predicate.negative()) // negated predicates not supported
1386
1387 auto expr = cast<const BinaryExpr>(&predicate.expr());
1388 if (not expr) // not a binary expression
1390
1391 bool has_attribute_left = is<const Designator>(expr->lhs);
1392 auto &attribute = has_attribute_left ? *expr->lhs : *expr->rhs;
1393 auto &constant = has_attribute_left ? *expr->rhs : *expr->lhs;
1394 if (not is<const Designator>(attribute)) // attribute must be on lhs
1396 if (not is_valid_bound(constant))
1398
1399 switch(expr->tok.type) {
1400 default:
1402 case TK_EQUAL:
1403 if (not has_lo_bound and not has_hi_bound) { // lo and hi bound not yet set
1404 has_lo_bound = has_hi_bound = true;
1405 } else {
1407 }
1408 break;
1409 case TK_GREATER:
1410 case TK_GREATER_EQUAL:
1411 if (has_attribute_left and not has_lo_bound) { // attribute on lhs, lo bound not yet set
1412 has_lo_bound = true;
1413 } else if (not has_attribute_left and not has_hi_bound) { // attribute on rhs, hi bound not yet set
1414 has_hi_bound = true;
1415 } else {
1417 }
1418 break;
1419 case TK_LESS:
1420 case TK_LESS_EQUAL:
1421 if (has_attribute_left and not has_hi_bound) { // attribute on lhs, hi bound not yet set
1422 has_hi_bound = true;
1423 } else if (not has_attribute_left and not has_lo_bound) { // attribute on rhs, lo bound not yet set
1424 has_lo_bound = true;
1425 } else {
1427 }
1428 break;
1429 }
1430 }
1431 return ConditionSet();
1432}
1433
1434template<idx::IndexMethod IndexMethod>
1436{
1437 ConditionSet post_cond;
1438
1439 /*----- Index scan does not introduce predication. -----*/
1440 post_cond.add_condition(Predicated(false));
1441
1442 /*----- Non-SIMDfied index scan does not introduce SIMD. -----*/
1443 post_cond.add_condition(NoSIMD());
1444
1445 /*----- Index scan introduces sortedness on indexed attribute. -----*/
1446 /* Extract identifier from cnf. */
1447 auto &cnf = M.filter.filter();
1448 Schema designators = cnf.get_required();
1449 M_insist(designators.num_entries() == 1, "filter condition must contain exactly one designator");
1450 Schema::Identifier &id = designators[0].id;
1451
1452 /* Add sortedness post condition. */
1453 Sortedness::order_t orders;
1454 orders.add(id, Sortedness::O_ASC);
1455 post_cond.add_condition(Sortedness(std::move(orders)));
1456
1457 return post_cond;
1458}
1459
1460template<idx::IndexMethod IndexMethod>
1462{
1463 /* TODO: add meaningful cost function. */
1464 return 0.0;
1465}
1466
1467template<idx::IndexMethod IndexMethod, typename Index, sql_type SqlT>
1468void index_scan_codegen_compilation(const Index &index, const index_scan_bounds_t &bounds,
1469 const Match<IndexScan<IndexMethod>> &M,
1470 setup_t setup, pipeline_t pipeline, teardown_t teardown) {
1471 using key_type = Index::key_type;
1472 using sql_type = SqlT;
1473
1475 /*----- Resolve callback function names. -----*/
1476 const char *scan_fn, *lower_bound_fn, *upper_bound_fn;
1477#define SET_CALLBACK_FNS(INDEX, KEY) \
1478 scan_fn = M_STR(idx_scan_##INDEX##_##KEY); \
1479 lower_bound_fn = M_STR(idx_lower_bound_##INDEX##_##KEY); \
1480 upper_bound_fn = M_STR(idx_upper_bound_##INDEX##_##KEY)
1481
1482#define RESOLVE_KEYTYPE(INDEX) \
1483 if constexpr(std::same_as<SqlT, _Boolx1>) { \
1484 SET_CALLBACK_FNS(INDEX, b); \
1485 } else if constexpr(std::same_as<sql_type, _I8x1>) { \
1486 SET_CALLBACK_FNS(INDEX, i1); \
1487 } else if constexpr(std::same_as<sql_type, _I16x1>) { \
1488 SET_CALLBACK_FNS(INDEX, i2); \
1489 } else if constexpr(std::same_as<sql_type, _I32x1>) { \
1490 SET_CALLBACK_FNS(INDEX, i4); \
1491 } else if constexpr(std::same_as<sql_type, _I64x1>) { \
1492 SET_CALLBACK_FNS(INDEX, i8); \
1493 } else if constexpr(std::same_as<sql_type, _Floatx1>) { \
1494 SET_CALLBACK_FNS(INDEX, f); \
1495 } else if constexpr(std::same_as<sql_type, _Doublex1>) { \
1496 SET_CALLBACK_FNS(INDEX, d); \
1497 } else if constexpr(std::same_as<sql_type, NChar>) { \
1498 SET_CALLBACK_FNS(IDNEX, p); \
1499 } else { \
1500 M_unreachable("incompatible SQL type"); \
1501 }
1502 if constexpr(is_specialization<Index, idx::ArrayIndex>) {
1503 RESOLVE_KEYTYPE(array)
1504 } else if constexpr(is_specialization<Index, idx::RecursiveModelIndex>) {
1505 RESOLVE_KEYTYPE(rmi)
1506 } else {
1507 M_unreachable("unknown index type");
1508 }
1509#undef RESOLVE_KEYTYPE
1510#undef SET_CALLBACK_FNS
1511
1512 /*----- Add index to context. -----*/
1514 U64x1 index_id(context.add_index(index));
1515
1516 /*----- Emit host calls to query the index for lo and hi bounds. -----*/
1517 auto compile_bound_lookup = [&](const ast::Expr &bound, bool is_lower_bound) {
1518 auto [constant, is_negative] = get_valid_bound(bound);
1519 auto c = Interpreter::eval(constant);
1520 key_type _key;
1521 if constexpr(m::boolean<key_type>) {
1522 _key = bool(c);
1523 M_insist(not is_negative, "boolean cannot be negative");
1524 } else if constexpr(m::integral<key_type>) {
1525 auto i64 = int64_t(c);
1526 M_insist(std::in_range<key_type>(i64), "integeral constant must be in range");
1527 _key = key_type(i64);
1528 _key = is_negative ? -_key : _key;
1529 } else if constexpr(std::same_as<float, key_type>) {
1530 auto d = double(c);
1531 _key = key_type(d);
1532 M_insist(_key == d, "downcasting should not impact precision");
1533 _key = is_negative ? -_key : _key;
1534 } else if constexpr(std::same_as<double, key_type>) {
1535 _key = double(c);
1536 _key = is_negative ? -_key : _key;
1537 } else if constexpr(std::same_as<const char*, key_type>) {
1538 _key = reinterpret_cast<const char*>(c.as_p());
1539 M_insist(not is_negative, "string cannot be negative");
1540 }
1541
1542 std::optional<typename sql_type::primitive_type> key;
1544 if constexpr (std::same_as<sql_type, NChar>) {
1545 key.emplace(CodeGenContext::Get().get_literal_address(_key));
1546 } else {
1547 key.emplace(_key);
1548 }
1550 /* If we materialize before calling the bound functions, the key parameter is independent of the bounds.
1551 * As a result, queries that only differ in the filter predicate are compiled to the exact same
1552 * WebAssembly code, enabling caching of compiled plans in V8. */
1553 if constexpr (std::same_as<sql_type, NChar>) {
1554 uint32_t *key_address = Module::Allocator().raw_malloc<uint32_t>();
1555 *key_address = CodeGenContext::Get().get_literal_raw_address(_key);
1556
1557 Ptr<U32x1> key_ptr(key_address);
1558 key.emplace(U32x1(*key_ptr).to<char*>(), false, as<const CharacterSequence>(bound.type()));
1559 } else {
1560 auto *key_address = Module::Allocator().raw_malloc<typename sql_type::type>();
1561 *key_address = _key;
1562
1563 Ptr<typename sql_type::primitive_type> key_ptr(key_address);
1564 key.emplace(*key_ptr);
1565 }
1566 } else {
1567 M_unreachable("unknown materialization strategy");
1568 }
1569 M_insist(bool(key), "key must be set");
1570 return Module::Get().emit_call<uint32_t>(
1571 /* fn= */ is_lower_bound ? lower_bound_fn : upper_bound_fn,
1572 /* index_id= */ index_id.clone(),
1573 /* key= */ *key
1574 );
1575 };
1576 Var<U32x1> lo(bool(bounds.lo) ? compile_bound_lookup(bounds.lo->get(), bounds.is_inclusive_lo)
1577 : U32x1(0));
1578 const Var<U32x1> hi(bool(bounds.hi) ? compile_bound_lookup(bounds.hi->get(), not bounds.is_inclusive_hi)
1579 : U32x1(index.num_entries()));
1580 Wasm_insist(lo <= hi, "bounds need to be valid");
1581
1582 /*----- Allocate memory for communication to host. -----*/
1583 M_insist(std::in_range<uint32_t>(M.batch_size), "should fit in uint32_t");
1584
1585 /* Determine alloc size as minimum of number of results and command-line parameter batch size, where 0 is
1586 * interpreted as infinity. */
1587 const Var<U32x1> alloc_size([&](){
1588 U32x1 num_results = hi - lo;
1589 U32x1 num_results_cpy = num_results.clone();
1590 U32x1 batch_size = M.batch_size == 0 ? num_results.clone() : U32x1(M.batch_size);
1591 U32x1 batch_size_cpy = batch_size.clone();
1592 return Select(batch_size < num_results, batch_size_cpy, num_results_cpy);
1593 }());
1594 Ptr<U32x1> buffer_address = Module::Allocator().malloc<uint32_t>(alloc_size);
1595
1596 /*----- Emit setup code *after* allocating memory to guarantee sequential memory allocation for pipeline. -----*/
1597 setup();
1598
1599 /*----- Emit loop code. -----*/
1600 Var<U32x1> num_tuples_in_batch;
1601 Var<Ptr<U32x1>> ptr;
1602 WHILE (lo < hi) {
1603 num_tuples_in_batch = Select(hi - lo > alloc_size, alloc_size, hi - lo);
1604 /* Call host to fill buffer memory with next batch of tuple ids. */
1605 Module::Get().emit_call<void>(
1606 /* fn= */ scan_fn,
1607 /* index_id= */ index_id,
1608 /* entry_offset= */ lo.val(),
1609 /* address= */ buffer_address.clone(),
1610 /* batch_size= */ num_tuples_in_batch.val()
1611 );
1612 lo += num_tuples_in_batch;
1613 ptr = buffer_address.clone();
1614 WHILE(num_tuples_in_batch > 0U) {
1615 static Schema empty_schema;
1617 /* tuple_value_schema= */ M.scan.schema(),
1618 /* tuple_address_schema= */ empty_schema,
1619 /* base_address= */ get_base_address(M.scan.store().table().name()),
1620 /* layout= */ M.scan.store().table().layout(),
1621 /* layout_schema= */ M.scan.store().table().schema(M.scan.alias()),
1622 /* tuple_id= */ *ptr
1623 );
1624 pipeline();
1625 num_tuples_in_batch -= 1U;
1626 ptr += 1;
1627 }
1628 }
1629
1630 /*----- Emit teardown code. -----*/
1631 teardown();
1632
1633 /*----- Free buffer memory. -----*/
1634 IF (alloc_size > U32x1(0)) { // only free if actually allocated
1635 Module::Allocator().free(buffer_address, alloc_size);
1636 };
1638 M_unreachable("not implemented yet");
1639 } else {
1640 M_unreachable("unknown compilation stategy");
1641 }
1642}
1643
1644template<idx::IndexMethod IndexMethod, typename Index>
1645void index_scan_codegen_interpretation(const Index &index, const index_scan_bounds_t &bounds,
1646 const Match<IndexScan<IndexMethod>> &M,
1647 setup_t setup, pipeline_t pipeline, teardown_t teardown)
1648{
1649 using key_type = Index::key_type;
1650
1651 static Schema empty_schema;
1652
1653 /*----- Interpret lo and hi bounds to retrieve index scan range -----*/
1654 auto interpret_and_lookup_bound = [&](const ast::Expr &bound, bool is_lower_bound) -> std::size_t {
1655 auto [constant, is_negative] = get_valid_bound(bound);
1656 auto c = Interpreter::eval(constant);
1657 key_type key;
1658 if constexpr(m::boolean<key_type>) {
1659 key = bool(c);
1660 M_insist(not is_negative, "boolean cannot be negative");
1661 } else if constexpr(m::integral<key_type>) {
1662 auto i64 = int64_t(c);
1663 M_insist(std::in_range<key_type>(i64), "integeral constant must be in range");
1664 key = key_type(i64);
1665 key = is_negative ? -key : key;
1666 } else if constexpr(std::same_as<float, key_type>) {
1667 auto d = double(c);
1668 key = key_type(d);
1669 M_insist(key == d, "downcasting should not impact precision");
1670 key = is_negative ? -key : key;
1671 } else if constexpr(std::same_as<double, key_type>) {
1672 key = double(c);
1673 key = is_negative ? -key : key;
1674 } else if constexpr(std::same_as<const char*, key_type>) {
1675 key = reinterpret_cast<const char*>(c.as_p());
1676 M_insist(not is_negative, "string cannot be negative");
1677 }
1678 return std::distance(index.begin(), is_lower_bound ? index.lower_bound(key)
1679 : index.upper_bound(key));
1680 };
1681 std::size_t lo = bool(bounds.lo) ? interpret_and_lookup_bound(bounds.lo->get(), bounds.is_inclusive_lo)
1682 : 0UL;
1683 std::size_t hi = bool(bounds.hi) ? interpret_and_lookup_bound(bounds.hi->get(), not bounds.is_inclusive_hi)
1684 : index.num_entries();
1685 M_insist(lo <= hi, "bounds need to be valid");
1686
1688 /*----- Allocate sufficient memory for results. -----*/
1689 uint32_t num_results = hi - lo;
1690 uint32_t *buffer_address = Module::Allocator().raw_malloc<uint32_t>(num_results + 1); // +1 for storing number of results itself
1691
1692 /*----- Perform index scan and fill memory with number of results and results. -----*/
1693 uint32_t *buffer_ptr = buffer_address;
1694 *buffer_ptr = num_results; // store in memory to enable caching
1695 ++buffer_ptr;
1696 for (auto it = index.begin() + lo; it != index.begin() + hi; ++it) {
1697 M_insist(std::in_range<uint32_t>(it->second), "tuple id must fit in uint32_t");
1698 *buffer_ptr = it->second;
1699 ++buffer_ptr;
1700 }
1701
1702 /*----- Emit setup code *after* allocating memory to guarantee sequential memory allocation for pipeline. -----*/
1703 setup();
1704
1705 /*----- Emit loop code. -----*/
1706 Ptr<U32x1> base(buffer_address + 1); // +1 to skip stored number of results
1707 Var<Ptr<U32x1>> ptr(base.clone());
1708 const Var<Ptr<U32x1>> end(base + U32x1(*Ptr<U32x1>(buffer_address)).make_signed());
1709 WHILE(ptr < end) {
1711 /* tuple_value_schema= */ M.scan.schema(),
1712 /* tuple_address_schema= */ empty_schema,
1713 /* base_address= */ get_base_address(M.scan.store().table().name()),
1714 /* layout= */ M.scan.store().table().layout(),
1715 /* layout_schema= */ M.scan.store().table().schema(M.scan.alias()),
1716 /* tuple_id= */ *ptr
1717 );
1718 pipeline();
1719 ptr += 1;
1720 }
1721
1722 /*----- Emit teardown code. -----*/
1723 teardown();
1725 /*----- Define function that emits code for loading and executing pipeline for a single tuple. -----*/
1726 FUNCTION(index_scan_parent_pipeline, void(uint32_t))
1727 {
1728 auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
1729
1730 /*----- Emit setup code. -----*/
1731 setup();
1732
1733 /*----- Load tuple. ----- */
1735 /* tuple_value_schema= */ M.scan.schema(),
1736 /* tuple_address_schema= */ empty_schema,
1737 /* base_address= */ get_base_address(M.scan.store().table().name()),
1738 /* layout= */ M.scan.store().table().layout(),
1739 /* layout_schema= */ M.scan.store().table().schema(M.scan.alias()),
1740 /* tuple_id= */ PARAMETER(0)
1741 );
1742
1743 /*----- Emit pipeline code. -----*/
1744 pipeline();
1745
1746 /*----- Emit teardown code. -----*/
1747 teardown();
1748 }
1749
1750 /*----- Perform index sequential scan, emit code to execute pipeline for each tuple. -----*/
1751 for (auto it = index.begin() + lo; it != index.begin() + hi; ++it) {
1752 M_insist(std::in_range<uint32_t>(it->second), "tuple id must fit in uint32_t");
1753 index_scan_parent_pipeline(uint32_t(it->second));
1754 }
1755 } else {
1756 M_unreachable("unknown materialization strategy");
1757 }
1758}
1759
1760template<idx::IndexMethod IndexMethod, typename Index, sql_type SqlT>
1761void index_scan_codegen_hybrid(const Index &index, const index_scan_bounds_t &bounds,
1762 const Match<IndexScan<IndexMethod>> &M,
1763 setup_t setup, pipeline_t pipeline, teardown_t teardown)
1764{
1765 using key_type = Index::key_type;
1766 using sql_type = SqlT;
1767
1768 /*----- Resolve callback function name. -----*/
1769 const char *scan_fn;
1770#define SET_CALLBACK_FNS(INDEX, KEY) \
1771 scan_fn = M_STR(idx_scan_##INDEX##_##KEY)
1772
1773#define RESOLVE_KEYTYPE(INDEX) \
1774 if constexpr(std::same_as<SqlT, _Boolx1>) { \
1775 SET_CALLBACK_FNS(INDEX, b); \
1776 } else if constexpr(std::same_as<sql_type, _I8x1>) { \
1777 SET_CALLBACK_FNS(INDEX, i1); \
1778 } else if constexpr(std::same_as<sql_type, _I16x1>) { \
1779 SET_CALLBACK_FNS(INDEX, i2); \
1780 } else if constexpr(std::same_as<sql_type, _I32x1>) { \
1781 SET_CALLBACK_FNS(INDEX, i4); \
1782 } else if constexpr(std::same_as<sql_type, _I64x1>) { \
1783 SET_CALLBACK_FNS(INDEX, i8); \
1784 } else if constexpr(std::same_as<sql_type, _Floatx1>) { \
1785 SET_CALLBACK_FNS(INDEX, f); \
1786 } else if constexpr(std::same_as<sql_type, _Doublex1>) { \
1787 SET_CALLBACK_FNS(INDEX, d); \
1788 } else if constexpr(std::same_as<sql_type, NChar>) { \
1789 SET_CALLBACK_FNS(IDNEX, p); \
1790 } else { \
1791 M_unreachable("incompatible SQL type"); \
1792 }
1793 if constexpr(is_specialization<Index, idx::ArrayIndex>) {
1794 RESOLVE_KEYTYPE(array)
1795 } else if constexpr(is_specialization<Index, idx::RecursiveModelIndex>) {
1796 RESOLVE_KEYTYPE(rmi)
1797 } else {
1798 M_unreachable("unknown index type");
1799 }
1800#undef RESOLVE_KEYTYPE
1801#undef SET_CALLBACK_FNS
1802
1803 /*----- Add index to context. -----*/
1805 U64x1 index_id(context.add_index(index));
1806
1807 /*----- Interpret lo and hi bounds to retrieve index scan range -----*/
1808 auto interpret_and_lookup_bound = [&](const ast::Expr &bound, bool is_lower_bound) -> std::size_t {
1809 auto [constant, is_negative] = get_valid_bound(bound);
1810 auto c = Interpreter::eval(constant);
1811 key_type key;
1812 if constexpr(m::boolean<key_type>) {
1813 key = bool(c);
1814 M_insist(not is_negative, "boolean cannot be negative");
1815 } else if constexpr(m::integral<key_type>) {
1816 auto i64 = int64_t(c);
1817 M_insist(std::in_range<key_type>(i64), "integeral constant must be in range");
1818 key = key_type(i64);
1819 key = is_negative ? -key : key;
1820 } else if constexpr(std::same_as<float, key_type>) {
1821 auto d = double(c);
1822 key = key_type(d);
1823 M_insist(key == d, "downcasting should not impact precision");
1824 key = is_negative ? -key : key;
1825 } else if constexpr(std::same_as<double, key_type>) {
1826 key = double(c);
1827 key = is_negative ? -key : key;
1828 } else if constexpr(std::same_as<const char*, key_type>) {
1829 key = reinterpret_cast<const char*>(c.as_p());
1830 M_insist(not is_negative, "string cannot be negative");
1831 }
1832 return std::distance(index.begin(), is_lower_bound ? index.lower_bound(key)
1833 : index.upper_bound(key));
1834 };
1835 std::size_t lo = bool(bounds.lo) ? interpret_and_lookup_bound(bounds.lo->get(), bounds.is_inclusive_lo)
1836 : 0UL;
1837 std::size_t hi = bool(bounds.hi) ? interpret_and_lookup_bound(bounds.hi->get(), not bounds.is_inclusive_hi)
1838 : index.num_entries();
1839 M_insist(lo <= hi, "bounds need to be valid");
1840 M_insist(std::in_range<uint32_t>(lo), "should fit in uint32_t");
1841 M_insist(std::in_range<uint32_t>(hi), "should fit in uint32_t");
1842
1843 /*----- Materialize offsets hi and lo. -----*/
1844 Var<U32x1> begin;
1845 std::optional<U32x1> end;
1847 begin = U32x1(lo);
1848 end.emplace(hi);
1850 /* If we materialize before allocating buffer memory, the addresses are independent of the buffer size. As a
1851 * result, queries that only differ in the filter predicate are compiled to the exact same WebAssembly code,
1852 * enabling caching of compiled plans in V8. */
1853 uint32_t *offset_address = Module::Allocator().raw_malloc<uint32_t>(2);
1854 offset_address[0] = uint32_t(lo);
1855 offset_address[1] = uint32_t(hi);
1856
1857 Ptr<U32x1> offset_ptr(offset_address);
1858 begin = *offset_ptr.clone();
1859 end.emplace(*(offset_ptr + 1));
1860 } else {
1861 M_unreachable("unknown materialization strategy");
1862 }
1863 M_insist(bool(end), "end must be set");
1864
1866 /*----- Allocate buffer memory for communication to host. -----*/
1867 M_insist(std::in_range<uint32_t>(M.batch_size), "should fit in uint32_t");
1868
1869 /* Determine alloc size as minimum of number of results and command-line parameter batch size, where 0 is
1870 * interpreted as infinity. */
1871 const Var<U32x1> alloc_size([&](){
1872 U32x1 num_results = end->clone() - begin;
1873 U32x1 num_results_cpy = num_results.clone();
1874 U32x1 batch_size = M.batch_size == 0 ? num_results.clone() : U32x1(M.batch_size);
1875 U32x1 batch_size_cpy = batch_size.clone();
1876 return Select(batch_size < num_results, batch_size_cpy, num_results_cpy);
1877 }());
1878 Ptr<U32x1> buffer_address = Module::Allocator().malloc<uint32_t>(alloc_size);
1879
1880 /*----- Emit setup code *after* allocating memory to guarantee sequential memory allocation for pipeline. -----*/
1881 setup();
1882
1883 /*----- Emit loop code. -----*/
1884 Var<U32x1> num_tuples_in_batch;
1885 Var<Ptr<U32x1>> ptr;
1886 WHILE (begin < end->clone()) {
1887 auto end_cpy = end->clone();
1888 num_tuples_in_batch = Select(*end - begin > alloc_size, alloc_size, end_cpy - begin);
1889 /* Call host to fill buffer memory with next batch of tuple ids. */
1890 Module::Get().emit_call<void>(
1891 /* fn= */ scan_fn,
1892 /* index_id= */ index_id,
1893 /* entry_offset= */ begin.val(),
1894 /* address= */ buffer_address.clone(),
1895 /* batch_size= */ num_tuples_in_batch.val()
1896 );
1897 begin += num_tuples_in_batch;
1898 ptr = buffer_address.clone();
1899 WHILE(num_tuples_in_batch > 0U) {
1900 static Schema empty_schema;
1902 /* tuple_value_schema= */ M.scan.schema(),
1903 /* tuple_address_schema= */ empty_schema,
1904 /* base_address= */ get_base_address(M.scan.store().table().name()),
1905 /* layout= */ M.scan.store().table().layout(),
1906 /* layout_schema= */ M.scan.store().table().schema(M.scan.alias()),
1907 /* tuple_id= */ *ptr
1908 );
1909 pipeline();
1910 num_tuples_in_batch -= 1U;
1911 ptr += 1;
1912 }
1913 }
1914
1915 /*----- Emit teardown code. -----*/
1916 teardown();
1917
1918 /*----- Free buffer memory. -----*/
1919 IF (alloc_size > U32x1(0)) { // only free if actually allocated
1920 Module::Allocator().free(buffer_address, alloc_size);
1921 };
1923 M_unreachable("not implemented yet");
1924 } else {
1925 M_unreachable("unknwon compilation strategy");
1926 }
1927}
1928
1930template<idx::IndexMethod IndexMethod, typename Index, sql_type SqlT>
1931void index_scan_resolve_strategy(const Index &index, const index_scan_bounds_t &bounds, const Match<IndexScan<IndexMethod>> &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
1932{
1934 index_scan_codegen_compilation<IndexMethod, Index, SqlT>(index, bounds, M, std::move(setup), std::move(pipeline), std::move(teardown));
1936 index_scan_codegen_interpretation<IndexMethod, Index>(index, bounds, M, std::move(setup), std::move(pipeline), std::move(teardown));
1938 index_scan_codegen_hybrid<IndexMethod, Index, SqlT>(index, bounds, M, std::move(setup), std::move(pipeline), std::move(teardown));
1939 } else {
1940 M_unreachable("invalid index access strategy");
1941 }
1942}
1943
1945template<idx::IndexMethod IndexMethod, typename AttrT, sql_type SqlT>
1947 setup_t setup, pipeline_t pipeline, teardown_t teardown)
1948{
1949 /*----- Lookup index. -----*/
1950 Catalog &C = Catalog::Get();
1951 auto &DB = C.get_database_in_use();
1952 auto &table_name = M.scan.store().table().name();
1953 auto &attribute_name = bounds.attribute.id.name;
1954 auto &index_base = DB.get_index(table_name, attribute_name, IndexMethod);
1955
1956 /*----- Resolve index type. -----*/
1957 if constexpr(IndexMethod == idx::IndexMethod::Array and requires { typename idx::ArrayIndex<AttrT>; }) {
1958 auto &index = as<const idx::ArrayIndex<AttrT>>(index_base);
1959 index_scan_resolve_strategy<IndexMethod, const idx::ArrayIndex<AttrT>, SqlT>(
1960 index, bounds, M, std::move(setup), std::move(pipeline), std::move(teardown)
1961 );
1962 } else if constexpr(IndexMethod == idx::IndexMethod::Rmi and requires { typename idx::RecursiveModelIndex<AttrT>; }) {
1963 auto &index = as<const idx::RecursiveModelIndex<AttrT>>(index_base);
1964 index_scan_resolve_strategy<IndexMethod, const idx::RecursiveModelIndex<AttrT>, SqlT>(
1965 index, bounds, M, std::move(setup), std::move(pipeline), std::move(teardown)
1966 );
1967 } else {
1968 M_unreachable("invalid index method");
1969 }
1970}
1971
1973template<idx::IndexMethod IndexMethod>
1975 setup_t setup, pipeline_t pipeline, teardown_t teardown)
1976{
1977 /*----- Extract bounds from CNF. -----*/
1978 auto &cnf = M.filter.filter();
1979 auto bounds = extract_index_scan_bounds(cnf);
1980
1981 /*----- Resolve attribute type. -----*/
1982#define RESOLVE_INDEX_METHOD(ATTRTYPE, SQLTYPE) \
1983 index_scan_resolve_index_method<IndexMethod, ATTRTYPE, SQLTYPE>(bounds, M, std::move(setup), std::move(pipeline), std::move(teardown))
1984
1986 [&](const Boolean&) { RESOLVE_INDEX_METHOD(bool, _Boolx1); },
1987 [&](const Numeric &n) {
1988 switch (n.kind) {
1989 case Numeric::N_Int:
1990 case Numeric::N_Decimal:
1991 switch (n.size()) {
1992 default: M_unreachable("invalid size");
1993 case 8: RESOLVE_INDEX_METHOD(int8_t, _I8x1); break;
1994 case 16: RESOLVE_INDEX_METHOD(int16_t, _I16x1); break;
1995 case 32: RESOLVE_INDEX_METHOD(int32_t, _I32x1); break;
1996 case 64: RESOLVE_INDEX_METHOD(int64_t, _I64x1); break;
1997 }
1998 break;
1999 case Numeric::N_Float:
2000 switch (n.size()) {
2001 default: M_unreachable("invalid size");
2002 case 32: RESOLVE_INDEX_METHOD(float, _Floatx1); break;
2003 case 64: RESOLVE_INDEX_METHOD(double, _Doublex1); break;
2004 }
2005 break;
2006 }
2007 },
2008 [&](const CharacterSequence&) { RESOLVE_INDEX_METHOD(const char*, NChar); },
2009 [&](const Date&) { RESOLVE_INDEX_METHOD(int32_t, _I32x1); },
2010 [&](const DateTime&) { RESOLVE_INDEX_METHOD(int64_t, _I64x1); },
2011 [](auto&&) { M_unreachable("invalid type"); },
2012 }, *bounds.attribute.type);
2013
2014#undef RESOLVE_INDEX_METHOD
2015}
2016
2017template<idx::IndexMethod IndexMethod>
2019 setup_t setup, pipeline_t pipeline, teardown_t teardown)
2020{
2021 auto &schema = M.scan.schema();
2022 M_insist(schema == schema.drop_constants().deduplicate(), "Schema of `ScanOperator` must neither contain NULL nor duplicates");
2023
2024 auto &table = M.scan.store().table();
2025 M_insist(not table.layout().is_finite(), "layout for `wasm::IndexScan` must be infinite");
2026
2033 index_scan_resolve_attribute_type(M, std::move(setup), std::move(pipeline), std::move(teardown));
2034}
2035
2036
2037/*======================================================================================================================
2038 * Filter
2039 *====================================================================================================================*/
2040
2041template<bool Predicated>
2042ConditionSet Filter<Predicated>::pre_condition(std::size_t child_idx, const std::tuple<const FilterOperator*>&)
2043{
2044 M_insist(child_idx == 0);
2045
2046 ConditionSet pre_cond;
2047
2048 if constexpr (not Predicated) {
2049 /*----- Branching filter does not support SIMD. -----*/
2050 pre_cond.add_condition(NoSIMD());
2051 }
2052
2053 return pre_cond;
2054}
2055
2056template<bool Predicated>
2058{
2059 ConditionSet post_cond(post_cond_child);
2060
2061 if constexpr (Predicated) {
2062 /*----- Predicated filter introduces predication. -----*/
2063 post_cond.add_or_replace_condition(m::Predicated(true));
2064 }
2065
2066 return post_cond;
2067}
2068
2069template<bool Predicated>
2071{
2072 const cnf::CNF &cond = M.filter.filter();
2073 const unsigned cost = std::accumulate(cond.cbegin(), cond.cend(), 0U, [](unsigned cost, const cnf::Clause &clause) {
2074 return cost + clause.size();
2075 });
2076 return cost;
2077}
2078
2079template<bool Predicated>
2081{
2082 /*----- Set minimal number of SIMD lanes preferred to get fully utilized SIMD vectors for the filter condition. --*/
2083 CodeGenContext::Get().update_num_simd_lanes_preferred(16); // set own preference
2084
2085 /*----- Execute filter. -----*/
2086 M.child->execute(
2087 /* setup= */ std::move(setup),
2088 /* pipeline= */ [&, pipeline=std::move(pipeline)](){
2089 if constexpr (Predicated) {
2090 CodeGenContext::Get().env().add_predicate(M.filter.filter());
2091 pipeline();
2092 } else {
2093 M_insist(CodeGenContext::Get().num_simd_lanes() == 1, "invalid number of SIMD lanes");
2094 IF (CodeGenContext::Get().env().compile<_Boolx1>(M.filter.filter()).is_true_and_not_null()) {
2095 pipeline();
2096 };
2097 }
2098 },
2099 /* teardown= */ std::move(teardown)
2100 );
2101}
2102
2103
2104/*======================================================================================================================
2105 * LazyDisjunctiveFilter
2106 *====================================================================================================================*/
2107
2108ConditionSet LazyDisjunctiveFilter::pre_condition(std::size_t child_idx, const std::tuple<const FilterOperator*>&)
2109{
2110 M_insist(child_idx == 0);
2111
2112 ConditionSet pre_cond;
2113
2114 /*----- Lazy disjunctive filter does not support SIMD. -----*/
2115 pre_cond.add_condition(NoSIMD());
2116
2117 return pre_cond;
2118}
2119
2121{
2122 const cnf::CNF &cond = M.filter.filter();
2123 M_insist(cond.size() == 1, "disjunctive filter condition must be a single clause");
2124 return cond[0].size() / 2.0; // on avg. half the number of predicates in the clause XXX consider selectivities
2125}
2126
2128 teardown_t teardown)
2129{
2130 const cnf::Clause &clause = M.filter.filter()[0];
2131
2132 M.child->execute(
2133 /* setup= */ std::move(setup),
2134 /* pipeline= */ [&, pipeline=std::move(pipeline)](){
2135 M_insist(CodeGenContext::Get().num_simd_lanes() == 1, "invalid number of SIMD lanes");
2136 BLOCK(lazy_disjunctive_filter)
2137 {
2138 BLOCK(lazy_disjunctive_filter_then)
2139 {
2140 for (const cnf::Predicate &pred : clause) {
2141 auto cond = CodeGenContext::Get().env().compile<_Boolx1>(*pred);
2142 if (pred.negative())
2143 GOTO(cond.is_false_and_not_null(), lazy_disjunctive_filter_then); // break to remainder of pipline
2144 else
2145 GOTO(cond.is_true_and_not_null(), lazy_disjunctive_filter_then); // break to remainder of pipline
2146 }
2147 GOTO(lazy_disjunctive_filter); // skip pipeline
2148 }
2149 pipeline();
2150 }
2151 },
2152 /* teardown= */ std::move(teardown)
2153 );
2154}
2155
2156
2157/*======================================================================================================================
2158 * Projection
2159 *====================================================================================================================*/
2160
2162 std::size_t child_idx,
2163 const std::tuple<const ProjectionOperator*> &partial_inner_nodes)
2164{
2165 M_insist(child_idx == 0);
2166
2167 ConditionSet pre_cond;
2168
2169 auto &projection = *std::get<0>(partial_inner_nodes);
2170
2171 if (not projection.children().empty()) { // projections starting a pipeline produce only a single tuple, i.e. no SIMD
2172 /*----- Projection does only support SIMD if all expressions can be computed using SIMD instructions. -----*/
2173 auto is_simd_computable = [](const ast::Expr &e){
2174 bool simd_computable = true;
2176 [&](const ast::BinaryExpr &b) -> void {
2177 if (b.lhs->type()->is_character_sequence() or b.rhs->type()->is_character_sequence()) {
2178 simd_computable = false; // string operations are not SIMDfiable
2179 throw visit_stop_recursion(); // abort recursion
2180 }
2181 if (b.common_operand_type->is_integral() and b.op().type == TK_SLASH) {
2182 simd_computable = false; // integer division is not SIMDfiable
2183 throw visit_stop_recursion(); // abort recursion
2184 }
2185 if (b.op().type == TK_PERCENT) {
2186 simd_computable = false; // modulo is not SIMDfiable
2187 throw visit_stop_recursion(); // abort recursion
2188 }
2189 },
2190 [](auto&) -> void {
2191 /* designators, constants, unary expressions, NULL(), INT(), already computed aggregates and results
2192 * of a nested query are SIMDfiable; nothing to be done */
2193 },
2195 return simd_computable;
2196 };
2197 auto pred = [&](auto &p){ return not is_simd_computable(p.first); };
2198 if (std::any_of(projection.projections().cbegin(), projection.projections().cend(), pred))
2199 pre_cond.add_condition(NoSIMD());
2200 }
2201
2202 return pre_cond;
2203}
2204
2206{
2207 ConditionSet post_cond(post_cond_child);
2208
2209 /*----- Project and rename in duplicated post condition. -----*/
2210 M_insist(M.projection.projections().size() == M.projection.schema().num_entries(),
2211 "projections must match the operator's schema");
2212 std::vector<std::pair<Schema::Identifier, Schema::Identifier>> old2new;
2213 auto p = M.projection.projections().begin();
2214 for (auto &e: M.projection.schema()) {
2215 auto pred = [&e](const auto &p) { return p.second == e.id; };
2216 if (std::find_if(old2new.cbegin(), old2new.cend(), pred) == old2new.cend()) {
2217 M_insist(p != M.projection.projections().end());
2218 old2new.emplace_back(Schema::Identifier(p->first.get()), e.id);
2219 }
2220 ++p;
2221 }
2222 post_cond.project_and_rename(old2new);
2223
2224 if (not M.child) {
2225 /*----- Leaf projection does not introduce predication. -----*/
2226 post_cond.add_condition(Predicated(false));
2227
2228 /*----- Leaf projection does not introduce SIMD. -----*/
2229 post_cond.add_condition(NoSIMD());
2230 }
2231
2232 return post_cond;
2233}
2234
2235void Projection::execute(const Match<Projection> &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
2236{
2237 auto execute_projection = [&, pipeline=std::move(pipeline)](){
2238 auto &old_env = CodeGenContext::Get().env();
2239 Environment new_env; // fresh environment
2240
2241 /*----- If predication is used, move predicate to newly created environment. -----*/
2242 if (old_env.predicated())
2243 new_env.add_predicate(old_env.extract_predicate());
2244
2245 /*----- Add projections to newly created environment. -----*/
2246 M_insist(M.projection.projections().size() == M.projection.schema().num_entries(),
2247 "projections must match the operator's schema");
2248 auto p = M.projection.projections().begin();
2249 for (auto &e: M.projection.schema()) {
2250 if (not new_env.has(e.id) and not e.id.is_constant()) { // no duplicate and no constant
2251 if (old_env.has(e.id)) {
2252 /*----- Migrate compiled expression to new context. ------*/
2253 new_env.add(e.id, old_env.get(e.id)); // to retain `e.id` for later compilation of expressions
2254 } else {
2255 /*----- Compile expression. -----*/
2256 M_insist(p != M.projection.projections().end());
2257 std::visit(overloaded {
2258 [&]<typename T, std::size_t L>(Expr<T, L> value) -> void {
2259 if (value.can_be_null()) {
2260 Var<Expr<T, L>> var(value); // introduce variable s.t. uses only load from it
2261 new_env.add(e.id, var);
2262 } else {
2263 /* introduce variable w/o NULL bit s.t. uses only load from it */
2264 Var<PrimitiveExpr<T, L>> var(value.insist_not_null());
2265 new_env.add(e.id, Expr<T, L>(var));
2266 }
2267 },
2268 [&](NChar value) -> void {
2269 Var<Ptr<Charx1>> var(value.val()); // introduce variable s.t. uses only load from it
2270 new_env.add(e.id, NChar(var, value.can_be_null(), value.length(),
2271 value.guarantees_terminating_nul()));
2272 },
2273 [](std::monostate) -> void { M_unreachable("invalid expression"); },
2274 }, old_env.compile(p->first));
2275 }
2276 }
2277 ++p;
2278 }
2279
2280 /*----- Resume pipeline with newly created environment. -----*/
2281 {
2282 auto S = CodeGenContext::Get().scoped_environment(std::move(new_env));
2283 pipeline();
2284 }
2285 };
2286
2287 if (M.child) {
2288 /*----- Set minimal number of SIMD lanes preferred to get fully utilized SIMD vectors *after* the projection. */
2289 uint64_t min_size_in_bytes = 16;
2290 for (auto &p : M.projection.projections()) {
2292 [](const m::ast::ErrorExpr&) -> void { M_unreachable("no errors at this stage"); },
2293 [](const m::ast::Designator&) -> void { /* nothing to be done */ },
2294 [](const m::ast::Constant&) -> void { /* nothing to be done */ },
2295 [](const m::ast::QueryExpr&) -> void { /* nothing to be done */ },
2296 [&min_size_in_bytes](const m::ast::FnApplicationExpr &fn) -> void {
2297 if (fn.get_function().is_aggregate())
2298 throw visit_skip_subtree(); // skip arguments to already computed aggregate
2299 min_size_in_bytes = std::min(min_size_in_bytes, (fn.type()->size() + 7) / 8);
2300 if (min_size_in_bytes == 1)
2301 throw visit_stop_recursion(); // abort recursion
2302 },
2303 [&min_size_in_bytes](auto &e) -> void { // i.e. for unary and binary expressions
2304 min_size_in_bytes = std::min(min_size_in_bytes, (e.type()->size() + 7) / 8);
2305 if (min_size_in_bytes == 1)
2306 throw visit_stop_recursion(); // abort recursion
2307 }
2308 }, p.first.get(), m::tag<m::ast::ConstPreOrderExprVisitor>());
2309 }
2310 CodeGenContext::Get().update_num_simd_lanes_preferred(16 / min_size_in_bytes); // set own preference
2311
2312 /*----- Execute projection. -----*/
2313 M.child->get()->execute(std::move(setup), std::move(execute_projection), std::move(teardown));
2314 } else {
2315 /*----- Execute projection. -----*/
2316 setup();
2317 CodeGenContext::Get().set_num_simd_lanes(1); // since only a single tuple is produced
2318 execute_projection();
2319 teardown();
2320 }
2321}
2322
2323
2324/*======================================================================================================================
2325 * Grouping
2326 *====================================================================================================================*/
2327
2328ConditionSet HashBasedGrouping::pre_condition(std::size_t child_idx, const std::tuple<const GroupingOperator*>&)
2329{
2330 M_insist(child_idx == 0);
2331
2332 ConditionSet pre_cond;
2333
2334 /*----- Hash-based grouping does not support SIMD. -----*/
2335 pre_cond.add_condition(NoSIMD());
2336
2337 return pre_cond;
2338}
2339
2341{
2342 return 1.5 * M.child->get_matched_root().info().estimated_cardinality;
2343}
2344
2346{
2347 ConditionSet post_cond;
2348
2349 /*----- Hash-based grouping does not introduce predication (it is already handled by the hash table). -----*/
2350 post_cond.add_condition(Predicated(false));
2351
2352 /*----- Hash-based grouping does not introduce SIMD. -----*/
2353 post_cond.add_condition(NoSIMD());
2354
2355 return post_cond;
2356}
2357
2359 teardown_t teardown)
2360{
2361 // TODO: determine setup
2362 const uint64_t AGGREGATES_SIZE_THRESHOLD_IN_BITS =
2363 M.use_in_place_values ? std::numeric_limits<uint64_t>::max() : 0;
2364
2365 const auto num_keys = M.grouping.group_by().size();
2366
2367 /*----- Compute hash table schema and information about aggregates, especially AVG aggregates. -----*/
2368 Schema ht_schema;
2369 /* Add key(s). */
2370 for (std::size_t i = 0; i < num_keys; ++i) {
2371 auto &e = M.grouping.schema()[i];
2372 ht_schema.add(e.id, e.type, e.constraints);
2373 }
2374 /* Add payload. */
2375 auto p = compute_aggregate_info(M.grouping.aggregates(), M.grouping.schema(), num_keys);
2376 const auto &aggregates = p.first;
2377 const auto &avg_aggregates = p.second;
2378 uint64_t aggregates_size_in_bits = 0;
2379 for (auto &info : aggregates) {
2380 ht_schema.add(info.entry);
2381 aggregates_size_in_bits += info.entry.type->size();
2382 }
2383
2384 /*----- Compute initial capacity of hash table. -----*/
2385 uint32_t initial_capacity = compute_initial_ht_capacity(M.grouping, M.load_factor);
2386
2387 /*----- Create hash table. -----*/
2388 std::unique_ptr<HashTable> ht;
2389 std::vector<HashTable::index_t> key_indices(num_keys);
2390 std::iota(key_indices.begin(), key_indices.end(), 0);
2391 if (M.use_open_addressing_hashing) {
2392 if (aggregates_size_in_bits < AGGREGATES_SIZE_THRESHOLD_IN_BITS)
2393 ht = std::make_unique<GlobalOpenAddressingInPlaceHashTable>(ht_schema, std::move(key_indices),
2394 initial_capacity);
2395 else
2396 ht = std::make_unique<GlobalOpenAddressingOutOfPlaceHashTable>(ht_schema, std::move(key_indices),
2397 initial_capacity);
2398 if (M.use_quadratic_probing)
2399 as<OpenAddressingHashTableBase>(*ht).set_probing_strategy<QuadraticProbing>();
2400 else
2401 as<OpenAddressingHashTableBase>(*ht).set_probing_strategy<LinearProbing>();
2402 } else {
2403 ht = std::make_unique<GlobalChainedHashTable>(ht_schema, std::move(key_indices), initial_capacity);
2404 }
2405
2406 /*----- Create child function. -----*/
2407 FUNCTION(hash_based_grouping_child_pipeline, void(void)) // create function for pipeline
2408 {
2409 auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
2410
2411 std::optional<HashTable::entry_t> dummy;
2412
2413 M.child->execute(
2414 /* setup= */ setup_t::Make_Without_Parent([&](){
2415 ht->setup();
2416 ht->set_high_watermark(M.load_factor);
2417 dummy.emplace(ht->dummy_entry()); // create dummy slot to ignore NULL values in aggregate computations
2418 }),
2419 /* pipeline= */ [&](){
2420 M_insist(bool(dummy));
2421 const auto &env = CodeGenContext::Get().env();
2422
2423 /*----- Insert key if not yet done. -----*/
2424 std::vector<SQL_t> key;
2425 for (auto &p : M.grouping.group_by())
2426 key.emplace_back(env.compile(p.first.get()));
2427 auto [entry, inserted] = ht->try_emplace(std::move(key));
2428
2429 /*----- Compute aggregates. -----*/
2430 Block init_aggs("hash_based_grouping.init_aggs", false),
2431 update_aggs("hash_based_grouping.update_aggs", false),
2432 update_avg_aggs("hash_based_grouping.update_avg_aggs", false);
2433 for (auto &info : aggregates) {
2434 bool is_min = false;
2435 switch (info.fnid) {
2436 default:
2437 M_unreachable("unsupported aggregate function");
2438 case m::Function::FN_MIN:
2439 is_min = true; // set flag and delegate to MAX case
2440 case m::Function::FN_MAX: {
2441 M_insist(info.args.size() == 1,
2442 "MIN and MAX aggregate functions expect exactly one argument");
2443 const auto &arg = *info.args[0];
2444 std::visit(overloaded {
2445 [&]<sql_type _T>(HashTable::reference_t<_T> &&r) -> void
2446 requires (not (std::same_as<_T, _Boolx1> or std::same_as<_T, NChar>)) {
2447 using type = typename _T::type;
2448 using T = PrimitiveExpr<type>;
2449
2450 auto _arg = env.compile(arg);
2451 _T _new_val = convert<_T>(_arg);
2452
2453 BLOCK_OPEN(init_aggs) {
2454 auto [val_, is_null] = _new_val.clone().split();
2455 T val(val_); // due to structured binding and lambda closure
2456 IF (is_null) {
2457 auto neutral = is_min ? T(std::numeric_limits<type>::max())
2458 : T(std::numeric_limits<type>::lowest());
2459 r.clone().set_value(neutral); // initialize with neutral element +inf or -inf
2460 if (info.entry.nullable())
2461 r.clone().set_null(); // first value is NULL
2462 } ELSE {
2463 r.clone().set_value(val); // initialize with first value
2464 if (info.entry.nullable())
2465 r.clone().set_not_null(); // first value is not NULL
2466 };
2467 }
2468 BLOCK_OPEN(update_aggs) {
2469 if (_new_val.can_be_null()) {
2471 auto [new_val_, new_val_is_null_] = _new_val.split();
2472 auto [old_min_max_, old_min_max_is_null] = _T(r.clone()).split();
2473 const Var<Boolx1> new_val_is_null(new_val_is_null_); // due to multiple uses
2474
2475 auto chosen_r = Select(new_val_is_null, dummy->extract<_T>(info.entry.id),
2476 r.clone());
2477 if constexpr (std::floating_point<type>) {
2478 chosen_r.set_value(
2479 is_min ? min(old_min_max_, new_val_) // update old min with new value
2480 : max(old_min_max_, new_val_) // update old max with new value
2481 ); // if new value is NULL, only dummy is written
2482 } else {
2483 const Var<T> new_val(new_val_),
2484 old_min_max(old_min_max_); // due to multiple uses
2485 auto cmp = is_min ? new_val < old_min_max : new_val > old_min_max;
2486#if 1
2487 chosen_r.set_value(
2488 Select(cmp,
2489 new_val, // update to new value
2490 old_min_max) // do not update
2491 ); // if new value is NULL, only dummy is written
2492#else
2493 IF (cmp) {
2494 r.set_value(new_val);
2495 };
2496#endif
2497 }
2498 r.set_null_bit(
2499 old_min_max_is_null and new_val_is_null // MIN/MAX is NULL iff all values are NULL
2500 );
2501 } else {
2502 auto new_val_ = _new_val.insist_not_null();
2503 auto old_min_max_ = _T(r.clone()).insist_not_null();
2504 if constexpr (std::floating_point<type>) {
2505 r.set_value(
2506 is_min ? min(old_min_max_, new_val_) // update old min with new value
2507 : max(old_min_max_, new_val_) // update old max with new value
2508 );
2509 } else {
2510 const Var<T> new_val(new_val_),
2511 old_min_max(old_min_max_); // due to multiple uses
2512 auto cmp = is_min ? new_val < old_min_max : new_val > old_min_max;
2513#if 1
2514 r.set_value(
2515 Select(cmp,
2516 new_val, // update to new value
2517 old_min_max) // do not update
2518 );
2519#else
2520 IF (cmp) {
2521 r.set_value(new_val);
2522 };
2523#endif
2524 }
2525 /* do not update NULL bit since it is already set to `false` */
2526 }
2527 }
2528 },
2529 []<sql_type _T>(HashTable::reference_t<_T>&&) -> void
2530 requires std::same_as<_T,_Boolx1> or std::same_as<_T, NChar> {
2531 M_unreachable("invalid type");
2532 },
2533 [](std::monostate) -> void { M_unreachable("invalid reference"); },
2534 }, entry.extract(info.entry.id));
2535 break;
2536 }
2537 case m::Function::FN_AVG: {
2538 auto it = avg_aggregates.find(info.entry.id);
2539 M_insist(it != avg_aggregates.end());
2540 const auto &avg_info = it->second;
2541 M_insist(avg_info.compute_running_avg,
2542 "AVG aggregate may only occur for running average computations");
2543 M_insist(info.args.size() == 1, "AVG aggregate function expects exactly one argument");
2544 const auto &arg = *info.args[0];
2545
2546 auto r = entry.extract<_Doublex1>(info.entry.id);
2547 auto _arg = env.compile(arg);
2548 _Doublex1 _new_val = convert<_Doublex1>(_arg);
2549
2550 BLOCK_OPEN(init_aggs) {
2551 auto [val_, is_null] = _new_val.clone().split();
2552 Doublex1 val(val_); // due to structured binding and lambda closure
2553 IF (is_null) {
2554 r.clone().set_value(Doublex1(0.0)); // initialize with neutral element 0
2555 if (info.entry.nullable())
2556 r.clone().set_null(); // first value is NULL
2557 } ELSE {
2558 r.clone().set_value(val); // initialize with first value
2559 if (info.entry.nullable())
2560 r.clone().set_not_null(); // first value is not NULL
2561 };
2562 }
2563 BLOCK_OPEN(update_avg_aggs) {
2564 /* Compute AVG as iterative mean as described in Knuth, The Art of Computer Programming
2565 * Vol 2, section 4.2.2. */
2566 if (_new_val.can_be_null()) {
2568 auto [new_val, new_val_is_null_] = _new_val.split();
2569 auto [old_avg_, old_avg_is_null] = _Doublex1(r.clone()).split();
2570 const Var<Boolx1> new_val_is_null(new_val_is_null_); // due to multiple uses
2571 const Var<Doublex1> old_avg(old_avg_); // due to multiple uses
2572
2573 auto delta_absolute = new_val - old_avg;
2574 auto running_count = _I64x1(entry.get<_I64x1>(avg_info.running_count)).insist_not_null();
2575 auto delta_relative = delta_absolute / running_count.to<double>();
2576
2577 auto chosen_r = Select(new_val_is_null, dummy->extract<_Doublex1>(info.entry.id),
2578 r.clone());
2579 chosen_r.set_value(
2580 old_avg + delta_relative // update old average with new value
2581 ); // if new value is NULL, only dummy is written
2582 r.set_null_bit(
2583 old_avg_is_null and new_val_is_null // AVG is NULL iff all values are NULL
2584 );
2585 } else {
2586 auto new_val = _new_val.insist_not_null();
2587 auto old_avg_ = _Doublex1(r.clone()).insist_not_null();
2588 const Var<Doublex1> old_avg(old_avg_); // due to multiple uses
2589
2590 auto delta_absolute = new_val - old_avg;
2591 auto running_count = _I64x1(entry.get<_I64x1>(avg_info.running_count)).insist_not_null();
2592 auto delta_relative = delta_absolute / running_count.to<double>();
2593 r.set_value(
2594 old_avg + delta_relative // update old average with new value
2595 );
2596 /* do not update NULL bit since it is already set to `false` */
2597 }
2598 }
2599 break;
2600 }
2601 case m::Function::FN_SUM: {
2602 M_insist(info.args.size() == 1, "SUM aggregate function expects exactly one argument");
2603 const auto &arg = *info.args[0];
2604 std::visit(overloaded {
2605 [&]<sql_type _T>(HashTable::reference_t<_T> &&r) -> void
2606 requires (not (std::same_as<_T, _Boolx1> or std::same_as<_T, NChar>)) {
2607 using type = typename _T::type;
2608 using T = PrimitiveExpr<type>;
2609
2610 auto _arg = env.compile(arg);
2611 _T _new_val = convert<_T>(_arg);
2612
2613 BLOCK_OPEN(init_aggs) {
2614 auto [val_, is_null] = _new_val.clone().split();
2615 T val(val_); // due to structured binding and lambda closure
2616 IF (is_null) {
2617 r.clone().set_value(T(type(0))); // initialize with neutral element 0
2618 if (info.entry.nullable())
2619 r.clone().set_null(); // first value is NULL
2620 } ELSE {
2621 r.clone().set_value(val); // initialize with first value
2622 if (info.entry.nullable())
2623 r.clone().set_not_null(); // first value is not NULL
2624 };
2625 }
2626 BLOCK_OPEN(update_aggs) {
2627 if (_new_val.can_be_null()) {
2629 auto [new_val, new_val_is_null_] = _new_val.split();
2630 auto [old_sum, old_sum_is_null] = _T(r.clone()).split();
2631 const Var<Boolx1> new_val_is_null(new_val_is_null_); // due to multiple uses
2632
2633 auto chosen_r = Select(new_val_is_null, dummy->extract<_T>(info.entry.id),
2634 r.clone());
2635 chosen_r.set_value(
2636 old_sum + new_val // add new value to old sum
2637 ); // if new value is NULL, only dummy is written
2638 r.set_null_bit(
2639 old_sum_is_null and new_val_is_null // SUM is NULL iff all values are NULL
2640 );
2641 } else {
2642 auto new_val = _new_val.insist_not_null();
2643 auto old_sum = _T(r.clone()).insist_not_null();
2644 r.set_value(
2645 old_sum + new_val // add new value to old sum
2646 );
2647 /* do not update NULL bit since it is already set to `false` */
2648 }
2649 }
2650 },
2651 []<sql_type _T>(HashTable::reference_t<_T>&&) -> void
2652 requires std::same_as<_T,_Boolx1> or std::same_as<_T, NChar> {
2653 M_unreachable("invalid type");
2654 },
2655 [](std::monostate) -> void { M_unreachable("invalid reference"); },
2656 }, entry.extract(info.entry.id));
2657 break;
2658 }
2659 case m::Function::FN_COUNT: {
2660 M_insist(info.args.size() <= 1, "COUNT aggregate function expects at most one argument");
2661
2662 auto r = entry.get<_I64x1>(info.entry.id); // do not extract to be able to access for AVG case
2663
2664 if (info.args.empty()) {
2665 BLOCK_OPEN(init_aggs) {
2666 r.clone() = _I64x1(1); // initialize with 1 (for first value)
2667 }
2668 BLOCK_OPEN(update_aggs) {
2669 auto old_count = _I64x1(r.clone()).insist_not_null();
2670 r.set_value(
2671 old_count + int64_t(1) // increment old count by 1
2672 );
2673 /* do not update NULL bit since it is already set to `false` */
2674 }
2675 } else {
2676 const auto &arg = *info.args[0];
2677
2678 auto _arg = env.compile(arg);
2679 I64x1 new_val_not_null = not_null(_arg).to<int64_t>();
2680
2681 BLOCK_OPEN(init_aggs) {
2682 r.clone() = _I64x1(new_val_not_null.clone()); // initialize with 1 iff first value is present
2683 }
2684 BLOCK_OPEN(update_aggs) {
2685 auto old_count = _I64x1(r.clone()).insist_not_null();
2686 r.set_value(
2687 old_count + new_val_not_null // increment old count by 1 iff new value is present
2688 );
2689 /* do not update NULL bit since it is already set to `false` */
2690 }
2691 }
2692 break;
2693 }
2694 }
2695 }
2696
2697 /*----- If group has been inserted, initialize aggregates. Otherwise, update them. -----*/
2698 IF (inserted) {
2699 init_aggs.attach_to_current();
2700 } ELSE {
2701 update_aggs.attach_to_current();
2702 update_avg_aggs.attach_to_current(); // after others to ensure that running count is incremented before
2703 };
2704 },
2705 /* teardown= */ teardown_t::Make_Without_Parent([&](){ ht->teardown(); })
2706 );
2707 }
2708 hash_based_grouping_child_pipeline(); // call child function
2709
2710 auto &env = CodeGenContext::Get().env();
2711
2712 /*----- Process each computed group. -----*/
2713 setup_t(std::move(setup), [&](){ ht->setup(); })();
2714 ht->for_each([&, pipeline=std::move(pipeline)](HashTable::const_entry_t entry){
2715 /*----- Compute key schema to detect duplicated keys. -----*/
2716 Schema key_schema;
2717 for (std::size_t i = 0; i < num_keys; ++i) {
2718 auto &e = M.grouping.schema()[i];
2719 key_schema.add(e.id, e.type, e.constraints);
2720 }
2721
2722 /*----- Add computed group tuples to current environment. ----*/
2723 for (auto &e : M.grouping.schema().deduplicate()) {
2724 try {
2725 key_schema.find(e.id);
2726 } catch (invalid_argument&) {
2727 continue; // skip duplicated keys since they must not be used afterwards
2728 }
2729
2730 if (auto it = avg_aggregates.find(e.id);
2731 it != avg_aggregates.end() and not it->second.compute_running_avg)
2732 { // AVG aggregates which is not yet computed, divide computed sum with computed count
2733 auto &avg_info = it->second;
2734 auto sum = std::visit(overloaded {
2735 [&]<sql_type T>(HashTable::const_reference_t<T> &&r) -> _Doublex1
2736 requires (std::same_as<T, _I64x1> or std::same_as<T, _Doublex1>) {
2737 return T(r).template to<double>();
2738 },
2739 [](auto&&) -> _Doublex1 { M_unreachable("invalid type"); },
2740 [](std::monostate&&) -> _Doublex1 { M_unreachable("invalid reference"); },
2741 }, entry.get(avg_info.sum));
2742 auto count = _I64x1(entry.get<_I64x1>(avg_info.running_count)).insist_not_null().to<double>();
2743 auto avg = sum / count;
2744 if (avg.can_be_null()) {
2745 _Var<Doublex1> var(avg); // introduce variable s.t. uses only load from it
2746 env.add(e.id, var);
2747 } else {
2748 /* introduce variable w/o NULL bit s.t. uses only load from it */
2749 Var<Doublex1> var(avg.insist_not_null());
2750 env.add(e.id, _Doublex1(var));
2751 }
2752 } else { // part of key or already computed aggregate
2753 std::visit(overloaded {
2754 [&]<typename T>(HashTable::const_reference_t<Expr<T>> &&r) -> void {
2755 Expr<T> value = r;
2756 if (value.can_be_null()) {
2757 Var<Expr<T>> var(value); // introduce variable s.t. uses only load from it
2758 env.add(e.id, var);
2759 } else {
2760 /* introduce variable w/o NULL bit s.t. uses only load from it */
2761 Var<PrimitiveExpr<T>> var(value.insist_not_null());
2762 env.add(e.id, Expr<T>(var));
2763 }
2764 },
2765 [&](HashTable::const_reference_t<NChar> &&r) -> void {
2766 NChar value(r);
2767 Var<Ptr<Charx1>> var(value.val()); // introduce variable s.t. uses only load from it
2768 env.add(e.id, NChar(var, value.can_be_null(), value.length(),
2769 value.guarantees_terminating_nul()));
2770 },
2771 [](std::monostate&&) -> void { M_unreachable("invalid reference"); },
2772 }, entry.get(e.id)); // do not extract to be able to access for not-yet-computed AVG aggregates
2773 }
2774 }
2775
2776 /*----- Resume pipeline. -----*/
2777 pipeline();
2778 });
2779 teardown_t(std::move(teardown), [&](){ ht->teardown(); })();
2780}
2781
2783 std::size_t child_idx,
2784 const std::tuple<const GroupingOperator*> &partial_inner_nodes)
2785{
2786 M_insist(child_idx == 0);
2787
2788 ConditionSet pre_cond;
2789
2790 /*----- Ordered grouping needs the data sorted on the grouping key (in either order). -----*/
2791 Sortedness::order_t orders;
2792 for (auto &p : std::get<0>(partial_inner_nodes)->group_by()) {
2793 Schema::Identifier id(p.first);
2794 if (orders.find(id) == orders.cend())
2795 orders.add(std::move(id), Sortedness::O_UNDEF);
2796 }
2797 pre_cond.add_condition(Sortedness(std::move(orders)));
2798
2799 /*----- Ordered grouping does not support SIMD. -----*/
2800 pre_cond.add_condition(NoSIMD());
2801
2802 return pre_cond;
2803}
2804
2806{
2807 return 1.0 * M.child->get_matched_root().info().estimated_cardinality;
2808}
2809
2811{
2812 ConditionSet post_cond;
2813
2814 /*----- Ordered grouping does not introduce predication. -----*/
2815 post_cond.add_condition(Predicated(false));
2816
2817 /*----- Preserve order of child for grouping keys. -----*/
2818 Sortedness::order_t orders;
2819 const auto &sortedness_child = post_cond_child.get_condition<Sortedness>();
2820 for (auto &[expr, alias] : M.grouping.group_by()) {
2821 auto it = sortedness_child.orders().find(Schema::Identifier(expr));
2822 M_insist(it != sortedness_child.orders().cend());
2823 Schema::Identifier id = alias.has_value() ? Schema::Identifier(alias.assert_not_none())
2825 if (orders.find(id) == orders.cend())
2826 orders.add(std::move(id), it->second); // drop duplicate since it must not be used afterwards
2827 }
2828 post_cond.add_condition(Sortedness(std::move(orders)));
2829
2830 /*----- Ordered grouping does not introduce SIMD. -----*/
2831 post_cond.add_condition(NoSIMD());
2832
2833 return post_cond;
2834}
2835
2837{
2838 Environment results;
2839 const auto num_keys = M.grouping.group_by().size();
2840
2841 /*----- Compute key schema to detect duplicated keys. -----*/
2842 Schema key_schema;
2843 for (std::size_t i = 0; i < num_keys; ++i) {
2844 auto &e = M.grouping.schema()[i];
2845 key_schema.add(e.id, e.type, e.constraints);
2846 }
2847
2848 /*----- Compute information about aggregates, especially about AVG aggregates. -----*/
2849 auto p = compute_aggregate_info(M.grouping.aggregates(), M.grouping.schema(), num_keys);
2850 const auto &aggregates = p.first;
2851 const auto &avg_aggregates = p.second;
2852
2853 /*----- Forward declare function to emit a group tuple in the current environment and resume the pipeline. -----*/
2854 FunctionProxy<void(void)> emit_group_and_resume_pipeline("emit_group_and_resume_pipeline");
2855
2856 std::optional<Var<Boolx1>> first_iteration;
2858 Global<Boolx1> first_iteration_backup(true);
2859
2860 using agg_t = agg_t_<false>;
2861 using agg_backup_t = agg_t_<true>;
2862 agg_t agg_values[aggregates.size()];
2863 agg_backup_t agg_value_backups[aggregates.size()];
2864
2865 using key_t = key_t_<false>;
2866 using key_backup_t = key_t_<true>;
2867 key_t key_values[num_keys];
2868 key_backup_t key_value_backups[num_keys];
2869
2870 auto store_locals_to_globals = [&](){
2871 /*----- Store local aggregate values to globals to access them in other function. -----*/
2872 for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
2873 auto &info = aggregates[idx];
2874
2875 switch (info.fnid) {
2876 default:
2877 M_unreachable("unsupported aggregate function");
2878 case m::Function::FN_MIN:
2879 case m::Function::FN_MAX: {
2880 auto min_max = [&]<typename T>() {
2881 auto &[min_max, is_null] = *M_notnull((
2882 std::get_if<std::pair<Var<PrimitiveExpr<T>>, std::optional<Var<Boolx1>>>>(&agg_values[idx])
2883 ));
2884 auto &[min_max_backup, is_null_backup] = *M_notnull((
2885 std::get_if<std::pair<Global<PrimitiveExpr<T>>,
2886 std::optional<Global<Boolx1>>>>(&agg_value_backups[idx])
2887 ));
2888 M_insist(bool(is_null) == bool(is_null_backup));
2889
2890 min_max_backup = min_max;
2891 if (is_null)
2892 *is_null_backup = *is_null;
2893 };
2894 auto &n = as<const Numeric>(*info.entry.type);
2895 switch (n.kind) {
2896 case Numeric::N_Int:
2897 case Numeric::N_Decimal:
2898 switch (n.size()) {
2899 default: M_unreachable("invalid size");
2900 case 8: min_max.template operator()<int8_t >(); break;
2901 case 16: min_max.template operator()<int16_t>(); break;
2902 case 32: min_max.template operator()<int32_t>(); break;
2903 case 64: min_max.template operator()<int64_t>(); break;
2904 }
2905 break;
2906 case Numeric::N_Float:
2907 if (n.size() <= 32)
2908 min_max.template operator()<float>();
2909 else
2910 min_max.template operator()<double>();
2911 }
2912 break;
2913 }
2914 case m::Function::FN_AVG: {
2915 auto &[avg, is_null] = *M_notnull((
2916 std::get_if<std::pair<Var<Doublex1>, std::optional<Var<Boolx1>>>>(&agg_values[idx])
2917 ));
2918 auto &[avg_backup, is_null_backup] = *M_notnull((
2919 std::get_if<std::pair<Global<Doublex1>, std::optional<Global<Boolx1>>>>(&agg_value_backups[idx])
2920 ));
2921 M_insist(bool(is_null) == bool(is_null_backup));
2922
2923 avg_backup = avg;
2924 if (is_null)
2925 *is_null_backup = *is_null;
2926
2927 break;
2928 }
2929 case m::Function::FN_SUM: {
2930 M_insist(info.args.size() == 1, "SUM aggregate function expects exactly one argument");
2931 const auto &arg = *info.args[0];
2932
2933 auto sum = [&]<typename T>() {
2934 auto &[sum, is_null] = *M_notnull((
2935 std::get_if<std::pair<Var<PrimitiveExpr<T>>, std::optional<Var<Boolx1>>>>(&agg_values[idx])
2936 ));
2937 auto &[sum_backup, is_null_backup] = *M_notnull((
2938 std::get_if<std::pair<Global<PrimitiveExpr<T>>,
2939 std::optional<Global<Boolx1>>>>(&agg_value_backups[idx])
2940 ));
2941 M_insist(bool(is_null) == bool(is_null_backup));
2942
2943 sum_backup = sum;
2944 if (is_null)
2945 *is_null_backup = *is_null;
2946 };
2947 auto &n = as<const Numeric>(*info.entry.type);
2948 switch (n.kind) {
2949 case Numeric::N_Int:
2950 case Numeric::N_Decimal:
2951 switch (n.size()) {
2952 default: M_unreachable("invalid size");
2953 case 8: sum.template operator()<int8_t >(); break;
2954 case 16: sum.template operator()<int16_t>(); break;
2955 case 32: sum.template operator()<int32_t>(); break;
2956 case 64: sum.template operator()<int64_t>(); break;
2957 }
2958 break;
2959 case Numeric::N_Float:
2960 if (n.size() <= 32)
2961 sum.template operator()<float>();
2962 else
2963 sum.template operator()<double>();
2964 }
2965 break;
2966 }
2967 case m::Function::FN_COUNT: {
2968 auto &count = *M_notnull(std::get_if<Var<I64x1>>(&agg_values[idx]));
2969 auto &count_backup = *M_notnull(std::get_if<Global<I64x1>>(&agg_value_backups[idx]));
2970
2971 count_backup = count;
2972
2973 break;
2974 }
2975 }
2976 }
2977
2978 /*----- Store local key values to globals to access them in other function. -----*/
2979 auto store = [&]<typename T>(std::size_t idx) {
2980 auto &[key, is_null] = *M_notnull((
2981 std::get_if<std::pair<Var<PrimitiveExpr<T>>, std::optional<Var<Boolx1>>>>(&key_values[idx])
2982 ));
2983 auto &[key_backup, is_null_backup] = *M_notnull((
2984 std::get_if<std::pair<Global<PrimitiveExpr<T>>, std::optional<Global<Boolx1>>>>(&key_value_backups[idx])
2985 ));
2986 M_insist(bool(is_null) == bool(is_null_backup));
2987
2988 key_backup = key;
2989 if (is_null)
2990 *is_null_backup = *is_null;
2991 };
2992 for (std::size_t idx = 0; idx < num_keys; ++idx) {
2994 [&](const Boolean&) { store.template operator()<bool>(idx); },
2995 [&](const Numeric &n) {
2996 switch (n.kind) {
2997 case Numeric::N_Int:
2998 case Numeric::N_Decimal:
2999 switch (n.size()) {
3000 default: M_unreachable("invalid size");
3001 case 8: store.template operator()<int8_t >(idx); break;
3002 case 16: store.template operator()<int16_t>(idx); break;
3003 case 32: store.template operator()<int32_t>(idx); break;
3004 case 64: store.template operator()<int64_t>(idx); break;
3005 }
3006 break;
3007 case Numeric::N_Float:
3008 if (n.size() <= 32)
3009 store.template operator()<float>(idx);
3010 else
3011 store.template operator()<double>(idx);
3012 }
3013 },
3014 [&](const CharacterSequence &cs) {
3015 auto &key = *M_notnull(std::get_if<Var<Ptr<Charx1>>>(&key_values[idx]));
3016 auto &key_backup = *M_notnull(std::get_if<Global<Ptr<Charx1>>>(&key_value_backups[idx]));
3017
3018 key_backup = key;
3019 },
3020 [&](const Date&) { store.template operator()<int32_t>(idx); },
3021 [&](const DateTime&) { store.template operator()<int64_t>(idx); },
3022 [](auto&&) { M_unreachable("invalid type"); },
3023 }, *M.grouping.schema()[idx].type);
3024 }
3025 };
3026
3027 M.child->execute(
3028 /* setup= */ setup_t::Make_Without_Parent([&](){
3029 first_iteration.emplace(first_iteration_backup);
3030
3031 /*----- Initialize aggregates and their backups. -----*/
3032 for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
3033 auto &info = aggregates[idx];
3034 const bool nullable = info.entry.nullable();
3035
3036 bool is_min = false;
3037 switch (info.fnid) {
3038 default:
3039 M_unreachable("unsupported aggregate function");
3040 case m::Function::FN_MIN:
3041 is_min = true; // set flag and delegate to MAX case
3042 case m::Function::FN_MAX: {
3043 auto min_max = [&]<typename T>() {
3044 auto neutral = is_min ? std::numeric_limits<T>::max()
3045 : std::numeric_limits<T>::lowest();
3046
3047 Var<PrimitiveExpr<T>> min_max;
3048 Global<PrimitiveExpr<T>> min_max_backup(neutral); // initialize with neutral element +inf or -inf
3049 std::optional<Var<Boolx1>> is_null;
3050 std::optional<Global<Boolx1>> is_null_backup;
3051
3052 /*----- Set local aggregate variables to global backups. -----*/
3053 min_max = min_max_backup;
3054 if (nullable) {
3055 is_null_backup.emplace(true); // MIN/MAX is initially NULL
3056 is_null.emplace(*is_null_backup);
3057 }
3058
3059 /*----- Add global aggregate to result environment to access it in other function. -----*/
3060 if (nullable)
3061 results.add(info.entry.id, Select(*is_null_backup, Expr<T>::Null(), min_max_backup));
3062 else
3063 results.add(info.entry.id, min_max_backup.val());
3064
3065 /*----- Move aggregate variables to access them later. ----*/
3066 new (&agg_values[idx]) agg_t(std::make_pair(std::move(min_max), std::move(is_null)));
3067 new (&agg_value_backups[idx]) agg_backup_t(std::make_pair(
3068 std::move(min_max_backup), std::move(is_null_backup)
3069 ));
3070 };
3071 auto &n = as<const Numeric>(*info.entry.type);
3072 switch (n.kind) {
3073 case Numeric::N_Int:
3074 case Numeric::N_Decimal:
3075 switch (n.size()) {
3076 default: M_unreachable("invalid size");
3077 case 8: min_max.template operator()<int8_t >(); break;
3078 case 16: min_max.template operator()<int16_t>(); break;
3079 case 32: min_max.template operator()<int32_t>(); break;
3080 case 64: min_max.template operator()<int64_t>(); break;
3081 }
3082 break;
3083 case Numeric::N_Float:
3084 if (n.size() <= 32)
3085 min_max.template operator()<float>();
3086 else
3087 min_max.template operator()<double>();
3088 }
3089 break;
3090 }
3091 case m::Function::FN_AVG: {
3092 Var<Doublex1> avg;
3093 Global<Doublex1> avg_backup(0.0); // initialize with neutral element 0
3094 std::optional<Var<Boolx1>> is_null;
3095 std::optional<Global<Boolx1>> is_null_backup;
3096
3097 /*----- Set local aggregate variables to global backups. -----*/
3098 avg = avg_backup;
3099 if (nullable) {
3100 is_null_backup.emplace(true); // AVG is initially NULL
3101 is_null.emplace(*is_null_backup);
3102 }
3103
3104 /*----- Add global aggregate to result environment to access it in other function. -----*/
3105 if (nullable)
3106 results.add(info.entry.id, Select(*is_null_backup, _Doublex1::Null(), avg_backup));
3107 else
3108 results.add(info.entry.id, avg_backup.val());
3109
3110 /*----- Move aggregate variables to access them later. ----*/
3111 new (&agg_values[idx]) agg_t(std::make_pair(std::move(avg), std::move(is_null)));
3112 new (&agg_value_backups[idx]) agg_backup_t(std::make_pair(
3113 std::move(avg_backup), std::move(is_null_backup)
3114 ));
3115
3116 break;
3117 }
3118 case m::Function::FN_SUM: {
3119 auto sum = [&]<typename T>() {
3121 Global<PrimitiveExpr<T>> sum_backup(T(0)); // initialize with neutral element 0
3122 std::optional<Var<Boolx1>> is_null;
3123 std::optional<Global<Boolx1>> is_null_backup;
3124
3125 /*----- Set local aggregate variables to global backups. -----*/
3126 sum = sum_backup;
3127 if (nullable) {
3128 is_null_backup.emplace(true); // SUM is initially NULL
3129 is_null.emplace(*is_null_backup);
3130 }
3131
3132 /*----- Add global aggregate to result environment to access it in other function. -----*/
3133 if (nullable)
3134 results.add(info.entry.id, Select(*is_null_backup, Expr<T>::Null(), sum_backup));
3135 else
3136 results.add(info.entry.id, sum_backup.val());
3137
3138 /*----- Move aggregate variables to access them later. ----*/
3139 new (&agg_values[idx]) agg_t(std::make_pair(std::move(sum), std::move(is_null)));
3140 new (&agg_value_backups[idx]) agg_backup_t(std::make_pair(
3141 std::move(sum_backup), std::move(is_null_backup)
3142 ));
3143 };
3144 auto &n = as<const Numeric>(*info.entry.type);
3145 switch (n.kind) {
3146 case Numeric::N_Int:
3147 case Numeric::N_Decimal:
3148 switch (n.size()) {
3149 default: M_unreachable("invalid size");
3150 case 8: sum.template operator()<int8_t >(); break;
3151 case 16: sum.template operator()<int16_t>(); break;
3152 case 32: sum.template operator()<int32_t>(); break;
3153 case 64: sum.template operator()<int64_t>(); break;
3154 }
3155 break;
3156 case Numeric::N_Float:
3157 if (n.size() <= 32)
3158 sum.template operator()<float>();
3159 else
3160 sum.template operator()<double>();
3161 }
3162 break;
3163 }
3164 case m::Function::FN_COUNT: {
3165 Var<I64x1> count;
3166 Global<I64x1> count_backup(0); // initialize with neutral element 0
3167 /* no `is_null` variables needed since COUNT will not be NULL */
3168
3169 /*----- Set local aggregate variable to global backup. -----*/
3170 count = count_backup;
3171
3172 /*----- Add global aggregate to result environment to access it in other function. -----*/
3173 results.add(info.entry.id, count_backup.val());
3174
3175 /*----- Move aggregate variables to access them later. ----*/
3176 new (&agg_values[idx]) agg_t(std::move(count));
3177 new (&agg_value_backups[idx]) agg_backup_t(std::move(count_backup));
3178
3179 break;
3180 }
3181 }
3182 }
3183
3184 /*----- Initialize keys and their backups. -----*/
3185 auto init = [&]<typename T>(std::size_t idx) {
3186 const bool nullable = M.grouping.schema()[idx].nullable();
3187
3189 Global<PrimitiveExpr<T>> key_backup;
3190 std::optional<Var<Boolx1>> is_null;
3191 std::optional<Global<Boolx1>> is_null_backup;
3192
3193 /*----- Set local key variables to global backups. -----*/
3194 key = key_backup;
3195 if (nullable) {
3196 is_null_backup.emplace();
3197 is_null.emplace(*is_null_backup);
3198 }
3199
3200 try {
3201 auto id = M.grouping.schema()[idx].id;
3202 key_schema.find(id);
3203
3204 /*----- Add global key to result environment to access it in other function. -----*/
3205 if (nullable)
3206 results.add(id, Select(*is_null_backup, Expr<T>::Null(), key_backup));
3207 else
3208 results.add(id, key_backup.val());
3209 } catch (invalid_argument&) {
3210 /* skip adding to result environment for duplicate keys since they must not be used afterwards */
3211 }
3212
3213 /*----- Move key variables to access them later. ----*/
3214 new (&key_values[idx]) key_t(std::make_pair(std::move(key), std::move(is_null)));
3215 new (&key_value_backups[idx]) key_backup_t(std::make_pair(
3216 std::move(key_backup), std::move(is_null_backup)
3217 ));
3218 };
3219 for (std::size_t idx = 0; idx < num_keys; ++idx) {
3221 [&](const Boolean&) { init.template operator()<bool>(idx); },
3222 [&](const Numeric &n) {
3223 switch (n.kind) {
3224 case Numeric::N_Int:
3225 case Numeric::N_Decimal:
3226 switch (n.size()) {
3227 default: M_unreachable("invalid size");
3228 case 8: init.template operator()<int8_t >(idx); break;
3229 case 16: init.template operator()<int16_t>(idx); break;
3230 case 32: init.template operator()<int32_t>(idx); break;
3231 case 64: init.template operator()<int64_t>(idx); break;
3232 }
3233 break;
3234 case Numeric::N_Float:
3235 if (n.size() <= 32)
3236 init.template operator()<float>(idx);
3237 else
3238 init.template operator()<double>(idx);
3239 }
3240 },
3241 [&](const CharacterSequence &cs) {
3242 Var<Ptr<Charx1>> key;
3243 Global<Ptr<Charx1>> key_backup;
3244 /* no `is_null` variables needed since pointer types must not be NULL */
3245
3246 /*----- Set local key variable to global backup. -----*/
3247 key = key_backup;
3248
3249 try {
3250 auto id = M.grouping.schema()[idx].id;
3251 key_schema.find(id);
3252
3253 /*----- Add global key to result environment to access it in other function. -----*/
3254 NChar str(key_backup.val(), M.grouping.schema()[idx].nullable(), cs.length, cs.is_varying);
3255 results.add(id, std::move(str));
3256 } catch (invalid_argument&) {
3257 /* skip adding to result environment for duplicate keys since they must not be used
3258 * afterwards */
3259 }
3260
3261 /*----- Move key variables to access them later. ----*/
3262 new (&key_values[idx]) key_t(std::move(key));
3263 new (&key_value_backups[idx]) key_backup_t(std::move(key_backup));
3264 },
3265 [&](const Date&) { init.template operator()<int32_t>(idx); },
3266 [&](const DateTime&) { init.template operator()<int64_t>(idx); },
3267 [](auto&&) { M_unreachable("invalid type"); },
3268 }, *M.grouping.schema()[idx].type);
3269 }
3270 }),
3271 /* pipeline= */ [&](){
3272 auto &env = CodeGenContext::Get().env();
3273
3274 /*----- If predication is used, introduce pred. var. and update it before computing aggregates. -----*/
3275 std::optional<Var<Boolx1>> pred;
3276 if (env.predicated()) {
3277 M_insist(CodeGenContext::Get().num_simd_lanes() == 1, "invalid number of SIMD lanes");
3278 pred = env.extract_predicate<_Boolx1>().is_true_and_not_null();
3279 }
3280
3281 /*----- Compute aggregates. -----*/
3282 Block reset_aggs("ordered_grouping.reset_aggs", false),
3283 update_aggs("ordered_grouping.update_aggs", false),
3284 update_avg_aggs("ordered_grouping.update_avg_aggs", false);
3285 for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
3286 auto &info = aggregates[idx];
3287
3288 bool is_min = false;
3289 switch (info.fnid) {
3290 default:
3291 M_unreachable("unsupported aggregate function");
3292 case m::Function::FN_MIN:
3293 is_min = true; // set flag and delegate to MAX case
3294 case m::Function::FN_MAX: {
3295 M_insist(info.args.size() == 1, "MIN and MAX aggregate functions expect exactly one argument");
3296 const auto &arg = *info.args[0];
3297 auto min_max = [&]<typename T>() {
3298 auto neutral = is_min ? std::numeric_limits<T>::max()
3299 : std::numeric_limits<T>::lowest();
3300
3301 auto &[min_max, is_null] = *M_notnull((
3302 std::get_if<std::pair<Var<PrimitiveExpr<T>>, std::optional<Var<Boolx1>>>>(&agg_values[idx])
3303 ));
3304
3305 BLOCK_OPEN(reset_aggs) {
3306 min_max = neutral;
3307 if (is_null)
3308 is_null->set_true();
3309 }
3310
3311 BLOCK_OPEN(update_aggs) {
3312 auto _arg = env.compile(arg);
3313 Expr<T> _new_val = convert<Expr<T>>(_arg);
3314 M_insist(_new_val.can_be_null() == bool(is_null));
3315 if (_new_val.can_be_null()) {
3317 auto _new_val_pred = pred ? Select(*pred, _new_val, Expr<T>::Null()) : _new_val;
3318 auto [new_val_, new_val_is_null_] = _new_val_pred.split();
3319 const Var<Boolx1> new_val_is_null(new_val_is_null_); // due to multiple uses
3320
3321 if constexpr (std::floating_point<T>) {
3322 min_max = Select(new_val_is_null,
3323 min_max, // ignore NULL
3324 is_min ? min(min_max, new_val_) // update old min with new value
3325 : max(min_max, new_val_)); // update old max with new value
3326 } else {
3327 const Var<PrimitiveExpr<T>> new_val(new_val_); // due to multiple uses
3328 auto cmp = is_min ? new_val < min_max : new_val > min_max;
3329#if 1
3330 min_max = Select(new_val_is_null,
3331 min_max, // ignore NULL
3332 Select(cmp,
3333 new_val, // update to new value
3334 min_max)); // do not update
3335#else
3336 IF (not new_val_is_null and cmp) {
3337 min_max = new_val;
3338 };
3339#endif
3340 }
3341 *is_null = *is_null and new_val_is_null; // MIN/MAX is NULL iff all values are NULL
3342 } else {
3343 auto _new_val_pred = pred ? Select(*pred, _new_val, neutral) : _new_val;
3344 auto new_val_ = _new_val_pred.insist_not_null();
3345 if constexpr (std::floating_point<T>) {
3346 min_max = is_min ? min(min_max, new_val_) // update old min with new value
3347 : max(min_max, new_val_); // update old max with new value
3348 } else {
3349 const Var<PrimitiveExpr<T>> new_val(new_val_); // due to multiple uses
3350 auto cmp = is_min ? new_val < min_max : new_val > min_max;
3351#if 1
3352 min_max = Select(cmp,
3353 new_val, // update to new value
3354 min_max); // do not update
3355#else
3356 IF (cmp) {
3357 min_max = new_val;
3358 };
3359#endif
3360 }
3361 }
3362 }
3363 };
3364 auto &n = as<const Numeric>(*info.entry.type);
3365 switch (n.kind) {
3366 case Numeric::N_Int:
3367 case Numeric::N_Decimal:
3368 switch (n.size()) {
3369 default: M_unreachable("invalid size");
3370 case 8: min_max.template operator()<int8_t >(); break;
3371 case 16: min_max.template operator()<int16_t>(); break;
3372 case 32: min_max.template operator()<int32_t>(); break;
3373 case 64: min_max.template operator()<int64_t>(); break;
3374 }
3375 break;
3376 case Numeric::N_Float:
3377 if (n.size() <= 32)
3378 min_max.template operator()<float>();
3379 else
3380 min_max.template operator()<double>();
3381 }
3382 break;
3383 }
3384 case m::Function::FN_AVG:
3385 break; // skip here and handle later
3386 case m::Function::FN_SUM: {
3387 M_insist(info.args.size() == 1, "SUM aggregate function expects exactly one argument");
3388 const auto &arg = *info.args[0];
3389
3390 auto sum = [&]<typename T>() {
3391 auto &[sum, is_null] = *M_notnull((
3392 std::get_if<std::pair<Var<PrimitiveExpr<T>>, std::optional<Var<Boolx1>>>>(&agg_values[idx])
3393 ));
3394
3395 BLOCK_OPEN(reset_aggs) {
3396 sum = T(0);
3397 if (is_null)
3398 is_null->set_true();
3399 }
3400
3401 BLOCK_OPEN(update_aggs) {
3402 auto _arg = env.compile(arg);
3403 Expr<T> _new_val = convert<Expr<T>>(_arg);
3404 M_insist(_new_val.can_be_null() == bool(is_null));
3405 if (_new_val.can_be_null()) {
3407 auto _new_val_pred = pred ? Select(*pred, _new_val, Expr<T>::Null()) : _new_val;
3408 auto [new_val, new_val_is_null_] = _new_val_pred.split();
3409 const Var<Boolx1> new_val_is_null(new_val_is_null_); // due to multiple uses
3410
3411 sum += Select(new_val_is_null,
3412 T(0), // ignore NULL
3413 new_val); // add new value to old sum
3414 *is_null = *is_null and new_val_is_null; // SUM is NULL iff all values are NULL
3415 } else {
3416 auto _new_val_pred = pred ? Select(*pred, _new_val, T(0)) : _new_val;
3417 sum += _new_val_pred.insist_not_null(); // add new value to old sum
3418 }
3419 }
3420 };
3421 auto &n = as<const Numeric>(*info.entry.type);
3422 switch (n.kind) {
3423 case Numeric::N_Int:
3424 case Numeric::N_Decimal:
3425 switch (n.size()) {
3426 default: M_unreachable("invalid size");
3427 case 8: sum.template operator()<int8_t >(); break;
3428 case 16: sum.template operator()<int16_t>(); break;
3429 case 32: sum.template operator()<int32_t>(); break;
3430 case 64: sum.template operator()<int64_t>(); break;
3431 }
3432 break;
3433 case Numeric::N_Float:
3434 if (n.size() <= 32)
3435 sum.template operator()<float>();
3436 else
3437 sum.template operator()<double>();
3438 }
3439 break;
3440 }
3441 case m::Function::FN_COUNT: {
3442 M_insist(info.args.size() <= 1, "COUNT aggregate function expects at most one argument");
3443 M_insist(info.entry.type->is_integral() and info.entry.type->size() == 64);
3444
3445 auto &count = *M_notnull(std::get_if<Var<I64x1>>(&agg_values[idx]));
3446
3447 BLOCK_OPEN(reset_aggs) {
3448 count = int64_t(0);
3449 }
3450
3451 BLOCK_OPEN(update_aggs) {
3452 if (info.args.empty()) {
3453 count += pred ? pred->to<int64_t>() : I64x1(1); // increment old count by 1 iff `pred` is true
3454 } else {
3455 auto _new_val = env.compile(*info.args[0]);
3456 if (can_be_null(_new_val)) {
3458 I64x1 inc = pred ? (not_null(_new_val) and *pred).to<int64_t>()
3459 : not_null(_new_val).to<int64_t>();
3460 count += inc; // increment old count by 1 iff new value is present and `pred` is true
3461 } else {
3462 discard(_new_val); // since it is not needed in this case
3463 I64x1 inc = pred ? pred->to<int64_t>() : I64x1(1);
3464 count += inc; // increment old count by 1 iff new value is present and `pred` is true
3465 }
3466 }
3467 }
3468 break;
3469 }
3470 }
3471 }
3472
3473 /*----- Compute AVG aggregates after others to ensure that running count is already created. -----*/
3474 for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
3475 auto &info = aggregates[idx];
3476
3477 if (info.fnid == m::Function::FN_AVG) {
3478 M_insist(info.args.size() == 1, "AVG aggregate function expects exactly one argument");
3479 const auto &arg = *info.args[0];
3480 M_insist(info.entry.type->is_double());
3481
3482 auto it = avg_aggregates.find(info.entry.id);
3483 M_insist(it != avg_aggregates.end());
3484 const auto &avg_info = it->second;
3485 M_insist(avg_info.compute_running_avg,
3486 "AVG aggregate may only occur for running average computations");
3487
3488 auto &[avg, is_null] = *M_notnull((
3489 std::get_if<std::pair<Var<Doublex1>, std::optional<Var<Boolx1>>>>(&agg_values[idx])
3490 ));
3491
3492 BLOCK_OPEN(reset_aggs) {
3493 avg = 0.0;
3494 if (is_null)
3495 is_null->set_true();
3496 }
3497
3498 BLOCK_OPEN(update_avg_aggs) {
3499 /* Compute AVG as iterative mean as described in Knuth, The Art of Computer Programming
3500 * Vol 2, section 4.2.2. */
3501 auto running_count_idx = std::distance(
3502 aggregates.cbegin(),
3503 std::find_if(aggregates.cbegin(), aggregates.cend(), [&avg_info](const auto &info){
3504 return info.entry.id == avg_info.running_count;
3505 })
3506 );
3507 M_insist(0 <= running_count_idx and running_count_idx < aggregates.size());
3508 auto &running_count = *M_notnull(std::get_if<Var<I64x1>>(&agg_values[running_count_idx]));
3509
3510 auto _arg = env.compile(arg);
3511 _Doublex1 _new_val = convert<_Doublex1>(_arg);
3512 M_insist(_new_val.can_be_null() == bool(is_null));
3513 if (_new_val.can_be_null()) {
3515 auto _new_val_pred = pred ? Select(*pred, _new_val, _Doublex1::Null()) : _new_val;
3516 auto [new_val, new_val_is_null_] = _new_val_pred.split();
3517 const Var<Boolx1> new_val_is_null(new_val_is_null_); // due to multiple uses
3518
3519 auto delta_absolute = new_val - avg;
3520 auto delta_relative = delta_absolute / running_count.to<double>();
3521
3522 avg += Select(new_val_is_null,
3523 0.0, // ignore NULL
3524 delta_relative); // update old average with new value
3525 *is_null = *is_null and new_val_is_null; // AVG is NULL iff all values are NULL
3526 } else {
3527 auto _new_val_pred = pred ? Select(*pred, _new_val, avg) : _new_val;
3528 auto delta_absolute = _new_val_pred.insist_not_null() - avg;
3529 auto delta_relative = delta_absolute / running_count.to<double>();
3530
3531 avg += delta_relative; // update old average with new value
3532 }
3533 }
3534 }
3535 }
3536
3537 /*----- Compute whether new group starts and update key variables accordingly. -----*/
3538 std::optional<Boolx1> group_differs;
3539 Block update_keys("ordered_grouping.update_grouping_keys", false);
3540 for (std::size_t idx = 0; idx < num_keys; ++idx) {
3541 std::visit(overloaded {
3542 [&]<typename T>(Expr<T> value) -> void {
3543 auto &[key_val, key_is_null] = *M_notnull((
3544 std::get_if<std::pair<Var<PrimitiveExpr<T>>, std::optional<Var<Boolx1>>>>(&key_values[idx])
3545 ));
3546 M_insist(value.can_be_null() == bool(key_is_null));
3547
3548 if (value.can_be_null()) {
3550 auto [val, is_null] = value.clone().split();
3551 auto null_differs = is_null != *key_is_null;
3552 Boolx1 key_differs = null_differs or (not *key_is_null and val != key_val);
3553 if (group_differs)
3554 group_differs.emplace(key_differs or *group_differs);
3555 else
3556 group_differs.emplace(key_differs);
3557
3558 BLOCK_OPEN(update_keys) {
3559 std::tie(key_val, key_is_null) = value.split();
3560 }
3561 } else {
3562 Boolx1 key_differs = key_val != value.clone().insist_not_null();
3563 if (group_differs)
3564 group_differs.emplace(key_differs or *group_differs);
3565 else
3566 group_differs.emplace(key_differs);
3567
3568 BLOCK_OPEN(update_keys) {
3569 key_val = value.insist_not_null();
3570 }
3571 }
3572 },
3573 [&](NChar value) -> void {
3574 auto &key = *M_notnull(std::get_if<Var<Ptr<Charx1>>>(&key_values[idx]));
3575
3576 auto [key_addr, key_is_nullptr] = key.val().split();
3577 auto [addr, is_nullptr] = value.val().clone().split();
3578 auto addr_differs = strncmp(
3579 /* left= */ NChar(addr, value.can_be_null(), value.length(),
3580 value.guarantees_terminating_nul()),
3581 /* right= */ NChar(key_addr, value.can_be_null(), value.length(),
3582 value.guarantees_terminating_nul()),
3583 /* len= */ U32x1(value.length()),
3584 /* op= */ NE
3585 );
3586 auto [addr_differs_value, addr_differs_is_null] = addr_differs.split();
3587 addr_differs_is_null.discard(); // use potentially-null value but it is overruled if it is NULL
3588 auto nullptr_differs = is_nullptr != key_is_nullptr.clone();
3589 Boolx1 key_differs = nullptr_differs or (not key_is_nullptr and addr_differs_value);
3590 if (group_differs)
3591 group_differs.emplace(key_differs or *group_differs);
3592 else
3593 group_differs.emplace(key_differs);
3594
3595 BLOCK_OPEN(update_keys) {
3596 key = value.val();
3597 }
3598 },
3599 [](auto) -> void { M_unreachable("SIMDfication currently not supported"); },
3600 [](std::monostate) -> void { M_unreachable("invalid expression"); },
3601 }, env.compile(M.grouping.group_by()[idx].first.get()));
3602 }
3603 M_insist(bool(group_differs));
3604
3605 /*----- Resume pipeline with computed group iff new one starts and emit code to reset aggregates. ---*/
3606 M_insist(bool(first_iteration));
3607 Boolx1 cond = *first_iteration or *group_differs; // `group_differs` defaulted in first iteration but overruled anyway
3608 IF (pred ? Select(*pred, cond, false) : cond) { // ignore entries for which predication predicate is not fulfilled
3609 IF (not *first_iteration) {
3610 store_locals_to_globals();
3611 emit_group_and_resume_pipeline();
3612 reset_aggs.attach_to_current();
3613 };
3614 update_keys.attach_to_current();
3615 *first_iteration = false;
3616 };
3617
3618 /*----- Emit code to update aggregates. -----*/
3619 update_aggs.attach_to_current();
3620 update_avg_aggs.attach_to_current(); // after others to ensure that running count is incremented before
3621 },
3622 /* teardown= */ teardown_t::Make_Without_Parent([&](){
3623 store_locals_to_globals();
3624
3625 /*----- Destroy created aggregate values and their backups. -----*/
3626 for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
3627 agg_values[idx].~agg_t();
3628 agg_value_backups[idx].~agg_backup_t();
3629 }
3630
3631 M_insist(bool(first_iteration));
3632 first_iteration_backup = *first_iteration;
3633 first_iteration.reset();
3634 })
3635 );
3636
3637 /*----- If input was not empty, emit last group tuple in the current environment and resume the pipeline. -----*/
3638 IF (not first_iteration_backup) {
3639 emit_group_and_resume_pipeline();
3640 };
3641
3642 /*----- Delayed definition of function to emit group and resume pipeline (since result environment is needed). ---*/
3643 auto fn = emit_group_and_resume_pipeline.make_function(); // outside BLOCK_OPEN-macro to register as current function
3644 BLOCK_OPEN(fn.body()) {
3645 auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
3646 auto &env = CodeGenContext::Get().env();
3647
3648 /*----- Emit setup code *before* possibly introducing temporary boolean variables to not overwrite them. -----*/
3649 setup();
3650
3651 /*----- Add computed group tuple to current environment. ----*/
3652 for (auto &e : M.grouping.schema().deduplicate()) {
3653 try {
3654 key_schema.find(e.id);
3655 } catch (invalid_argument&) {
3656 continue; // skip duplicated keys since they must not be used afterwards
3657 }
3658
3659 if (auto it = avg_aggregates.find(e.id);
3660 it != avg_aggregates.end() and not it->second.compute_running_avg)
3661 { // AVG aggregates which is not yet computed, divide computed sum with computed count
3662 auto &avg_info = it->second;
3663 auto sum = results.get(avg_info.sum);
3664 auto count = results.get<_I64x1>(avg_info.running_count).insist_not_null().to<double>();
3665 auto avg = convert<_Doublex1>(sum) / count;
3666 if (avg.can_be_null()) {
3667 _Var<Doublex1> var(avg); // introduce variable s.t. uses only load from it
3668 env.add(e.id, var);
3669 } else {
3670 /* introduce variable w/o NULL bit s.t. uses only load from it */
3671 Var<Doublex1> var(avg.insist_not_null());
3672 env.add(e.id, _Doublex1(var));
3673 }
3674 } else { // part of key or already computed aggregate
3675 std::visit(overloaded {
3676 [&]<typename T>(Expr<T> value) -> void {
3677 if (value.can_be_null()) {
3678 Var<Expr<T>> var(value); // introduce variable s.t. uses only load from it
3679 env.add(e.id, var);
3680 } else {
3681 /* introduce variable w/o NULL bit s.t. uses only load from it */
3682 Var<PrimitiveExpr<T>> var(value.insist_not_null());
3683 env.add(e.id, Expr<T>(var));
3684 }
3685 },
3686 [&](NChar value) -> void {
3687 Var<Ptr<Charx1>> var(value.val()); // introduce variable s.t. uses only load from it
3688 env.add(e.id, NChar(var, value.can_be_null(), value.length(),
3689 value.guarantees_terminating_nul()));
3690 },
3691 [](auto) -> void { M_unreachable("SIMDfication currently not supported"); },
3692 [](std::monostate) -> void { M_unreachable("invalid reference"); },
3693 }, results.get(e.id)); // do not extract to be able to access for not-yet-computed AVG aggregates
3694 }
3695 }
3696
3697 /*----- Resume pipeline. -----*/
3698 pipeline();
3699
3700 /*----- Emit teardown code. -----*/
3701 teardown();
3702 }
3703}
3704
3705
3706/*======================================================================================================================
3707 * Aggregation
3708 *====================================================================================================================*/
3709
3710ConditionSet Aggregation::pre_condition(std::size_t child_idx, const std::tuple<const AggregationOperator*>&)
3711{
3712 M_insist(child_idx == 0);
3713
3714 ConditionSet pre_cond;
3715
3716 return pre_cond;
3717}
3718
3720{
3721 ConditionSet post_cond;
3722
3723 /*----- Aggregation does not introduce predication. -----*/
3724 post_cond.add_condition(Predicated(false));
3725
3726 /*----- Aggregation does implicitly sort the data since only one tuple is produced. -----*/
3727 Sortedness::order_t orders;
3728 for (auto &e : M.aggregation.schema().deduplicate())
3729 orders.add(e.id, Sortedness::O_UNDEF);
3730 post_cond.add_condition(Sortedness(std::move(orders)));
3731
3732 /*----- Aggregation does not introduce SIMD since only one tuple is produced. -----*/
3733 post_cond.add_condition(NoSIMD());
3734
3735 return post_cond;
3736}
3737
3739{
3740 Environment results;
3742 std::vector<std::function<void(void)>> finalize_aggregates;
3743
3744 /*----- Compute information about aggregates, especially about AVG aggregates. -----*/
3745 auto p = compute_aggregate_info(M.aggregation.aggregates(), M.aggregation.schema());
3746 const auto &aggregates = p.first;
3747 const auto &avg_aggregates = p.second;
3748
3749 /*----- Set minimal number of SIMD lanes preferred to get fully utilized SIMD vectors for the aggregate args. ----*/
3750 uint64_t min_size_in_bytes = 16;
3751 for (auto &fn : M.aggregation.aggregates()) {
3752 for (auto &e : fn.get().args) {
3754 [](const m::ast::ErrorExpr&) -> void { M_unreachable("no errors at this stage"); },
3755 [](const m::ast::Designator&) -> void { /* nothing to be done */ },
3756 [](const m::ast::Constant&) -> void { /* nothing to be done */ },
3757 [](const m::ast::QueryExpr&) -> void { /* nothing to be done */ },
3758 [&min_size_in_bytes](const m::ast::FnApplicationExpr &fn) -> void {
3759 M_insist(not fn.get_function().is_aggregate(), "aggregate arguments must not be aggregates");
3760 min_size_in_bytes = std::min(min_size_in_bytes, (fn.type()->size() + 7) / 8);
3761 if (min_size_in_bytes == 1)
3762 throw visit_stop_recursion(); // abort recursion
3763 },
3764 [&min_size_in_bytes](auto &e) -> void { // i.e. for unary and binary expressions
3765 min_size_in_bytes = std::min(min_size_in_bytes, (e.type()->size() + 7) / 8);
3766 if (min_size_in_bytes == 1)
3767 throw visit_stop_recursion(); // abort recursion
3768 }
3770 }
3771 }
3772 CodeGenContext::Get().update_num_simd_lanes_preferred(16 / min_size_in_bytes); // set own preference
3773
3774 /*----- Set minimal number of SIMD lanes preferred to be able to compute running averages. ----*/
3775 if (std::any_of(avg_aggregates.begin(), avg_aggregates.end(), [](auto &i){ return i.second.compute_running_avg; }))
3776 CodeGenContext::Get().update_num_simd_lanes_preferred(4); // set own preference
3777
3778 /*----- Create child function. -----*/
3779 FUNCTION(aggregation_child_pipeline, void(void)) // create function for pipeline
3780 {
3781 auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
3782
3783#ifndef NDEBUG
3784 std::size_t num_simd_lanes;
3785#endif
3786 void *_agg_values;
3787 void *_agg_value_backups;
3788
3789 M.child->execute(
3790 /* setup= */ setup_t::Make_Without_Parent([&]() {
3791 auto execute_setup = [&]<std::size_t L>() {
3792#ifndef NDEBUG
3793 num_simd_lanes = L;
3794#endif
3795
3796 /*----- Initialize aggregates helper structures. -----*/
3797 using agg_t = agg_t_<false, L>;
3798 using agg_backup_t = agg_t_<true, L>;
3799 auto agg_values = new agg_t[aggregates.size()];
3800 auto agg_value_backups = new agg_backup_t[aggregates.size()];
3801
3802 /*----- Store aggregates helper structures for pipeline and teardown callbacks. -----*/
3803 _agg_values = static_cast<void*>(agg_values);
3804 _agg_value_backups = static_cast<void*>(agg_value_backups);
3805
3806 /*----- Initialize aggregates and their backups. -----*/
3807 for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
3808 auto &info = aggregates[idx];
3809
3810 bool is_min = false;
3811 switch (info.fnid) {
3812 default:
3813 M_unreachable("unsupported aggregate function");
3814 case m::Function::FN_MIN:
3815 is_min = true; // set flag and delegate to MAX case
3816 case m::Function::FN_MAX: {
3817 auto min_max = [&]<typename T>() {
3818 auto neutral = is_min ? std::numeric_limits<T>::max()
3819 : std::numeric_limits<T>::lowest();
3820
3821 Var<PrimitiveExpr<T, L>> min_max;
3822 Global<PrimitiveExpr<T, L>> min_max_backup(
3823 neutral // initialize with neutral element +inf or -inf
3824 );
3826 Global<Bool<L>> is_null_backup(true); // MIN/MAX is initially NULL
3827
3828 /*----- Set local aggregate variables to global backups. -----*/
3829 min_max = min_max_backup;
3830 is_null = is_null_backup;
3831
3832 /*----- Add global aggregate to result env. to access it in other function. -----*/
3833 if constexpr (L == 1) { // scalar
3834 PrimitiveExpr<T> value = min_max_backup;
3835 Boolx1 is_null = is_null_backup;
3836 results.add(info.entry.id, Select(is_null, Expr<T>::Null(), value));
3837 } else { // vectorial
3838 /* Create lambda which emits the computation of the final *scalar* aggregate.
3839 * This can then be called in the pipeline function starting at the aggregation
3840 * operator s.t. the emitted variable is a local of the correct function.
3841 * Do not access the global variables inside the lambda using closure by
3842 * reference since they are already destroyed when the lambda will be called.
3843 * Instead, copy their values into the lambda. However, since DSL expressions
3844 * are not const-copy-constructible, we have to allocate them on the heap and
3845 * destroy them manually inside the lambda. */
3846 auto simd_min_max = new PrimitiveExpr<T, L>(min_max_backup.val());
3847 auto simd_is_null = new Bool<L>(is_null_backup.val());
3848 finalize_aggregates.emplace_back([&, is_min, simd_min_max, simd_is_null]() {
3849 PrimitiveExpr<T> value = [&]<std::size_t... Is>(std::index_sequence<Is...>) {
3850 Var<PrimitiveExpr<T>> res(simd_min_max->clone().template extract<0>());
3851 auto update = [&]<std::size_t I>(){
3852 if constexpr (requires (PrimitiveExpr<T> v) { min(v, v); max(v, v); }) {
3853 res = is_min ? min(res, simd_min_max->clone().template extract<I>())
3854 : max(res, simd_min_max->clone().template extract<I>());
3855 } else {
3856 const Var<PrimitiveExpr<T>> extracted(
3857 simd_min_max->clone().template extract<I>()
3858 ); // due to multiple uses
3859 auto cmp = is_min ? extracted < res : extracted > res;
3860#if 1
3861 res = Select(cmp, extracted, res);
3862#else
3863 IF (cmp) {
3864 res = extracted;
3865 };
3866#endif
3867 }
3868 };
3869 (update.template operator()<Is + 1>(), ...);
3870 return res;
3871 }(std::make_index_sequence<L - 1>{});
3872 simd_min_max->discard(); // since it was always cloned
3873 Boolx1 is_null = simd_is_null->all_true();
3874 results.add(info.entry.id, Select(is_null, Expr<T>::Null(), value));
3875 delete simd_min_max; // destroy heap-allocated variable
3876 delete simd_is_null; // destroy heap-allocated variable
3877 });
3878 }
3879
3880 /*----- Move aggregate variables to access them later. ----*/
3881 new (&agg_values[idx]) agg_t(std::make_pair(
3882 std::move(min_max), std::move(is_null))
3883 );
3884 new (&agg_value_backups[idx]) agg_backup_t(std::make_pair(
3885 std::move(min_max_backup), std::move(is_null_backup)
3886 ));
3887 };
3888 auto &n = as<const Numeric>(*info.entry.type);
3889 switch (n.kind) {
3890 case Numeric::N_Int:
3891 case Numeric::N_Decimal:
3892 switch (n.size()) {
3893 default: M_unreachable("invalid size");
3894 case 8: min_max.template operator()<int8_t >(); break;
3895 case 16: min_max.template operator()<int16_t>(); break;
3896 case 32: min_max.template operator()<int32_t>(); break;
3897 case 64: min_max.template operator()<int64_t>(); break;
3898 }
3899 break;
3900 case Numeric::N_Float:
3901 if (n.size() <= 32)
3902 min_max.template operator()<float>();
3903 else
3904 min_max.template operator()<double>();
3905 }
3906 break;
3907 }
3908 case m::Function::FN_AVG:
3909 break; // skip here and handle later
3910 case m::Function::FN_SUM: {
3911 auto sum = [&]<typename T>() {
3913 Global<PrimitiveExpr<T, L>> sum_backup(T(0)); // initialize with neutral element 0
3915 Global<Bool<L>> is_null_backup(true); // SUM is initially NULL
3916
3917 /*----- Set local aggregate variables to global backups. -----*/
3918 sum = sum_backup;
3919 is_null = is_null_backup;
3920
3921 /*----- Add global aggregate to result env. to access it in other function. -----*/
3922 if constexpr (L == 1) { // scalar
3923 PrimitiveExpr<T> value = sum_backup;
3924 Boolx1 is_null = is_null_backup;
3925 results.add(info.entry.id, Select(is_null, Expr<T>::Null(), value));
3926 } else { // vectorial
3927 PrimitiveExpr<T> value = [&]<std::size_t... Is>(std::index_sequence<Is...>) {
3928 return (sum_backup.template extract<Is>() + ...);
3929 }(std::make_index_sequence<L>{});
3930 Boolx1 is_null = is_null_backup.all_true();
3931 results.add(info.entry.id, Select(is_null, Expr<T>::Null(), value));
3932 }
3933
3934 /*----- Move aggregate variables to access them later. ----*/
3935 new (&agg_values[idx]) agg_t(std::make_pair(std::move(sum), std::move(is_null)));
3936 new (&agg_value_backups[idx]) agg_backup_t(std::make_pair(
3937 std::move(sum_backup), std::move(is_null_backup)
3938 ));
3939 };
3940 auto &n = as<const Numeric>(*info.entry.type);
3941 switch (n.kind) {
3942 case Numeric::N_Int:
3943 case Numeric::N_Decimal:
3944 switch (n.size()) {
3945 default: M_unreachable("invalid size");
3946 case 8: sum.template operator()<int8_t >(); break;
3947 case 16: sum.template operator()<int16_t>(); break;
3948 case 32: sum.template operator()<int32_t>(); break;
3949 case 64: sum.template operator()<int64_t>(); break;
3950 }
3951 break;
3952 case Numeric::N_Float:
3953 if (n.size() <= 32)
3954 sum.template operator()<float>();
3955 else
3956 sum.template operator()<double>();
3957 }
3958 break;
3959 }
3960 case m::Function::FN_COUNT: {
3961 Var<I64<L>> count;
3962 Global<I64<L>> count_backup(0); // initialize with neutral element 0
3963 /* no `is_null` variables needed since COUNT will not be NULL */
3964
3965 /*----- Set local aggregate variable to global backup. -----*/
3966 count = count_backup;
3967
3968 /*----- Add global aggregate to result env. to access it in other function. -----*/
3969 if constexpr (L == 1) { // scalar
3970 I64x1 value = count_backup;
3971 results.add(info.entry.id, value);
3972 } else { // vectorial
3973 I64x1 value = [&]<std::size_t... Is>(std::index_sequence<Is...>) {
3974 return (count_backup.template extract<Is>() + ...);
3975 }(std::make_index_sequence<L>{});
3976 results.add(info.entry.id, value);
3977 }
3978
3979 /*----- Move aggregate variables to access them later. ----*/
3980 new (&agg_values[idx]) agg_t(std::move(count));
3981 new (&agg_value_backups[idx]) agg_backup_t(std::move(count_backup));
3982
3983 break;
3984 }
3985 }
3986 }
3987
3988 /*----- Initialize AVG aggregates after others to ensure that running count is initialized before. */
3989 for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
3990 auto &info = aggregates[idx];
3991
3992 if (info.fnid == m::Function::FN_AVG) {
3993 Var<Double<L>> avg;
3994 Global<Double<L>> avg_backup(0.0); // initialize with neutral element 0
3996 Global<Bool<L>> is_null_backup(true); // AVG is initially NULL
3997
3998 /*----- Set local aggregate variables to global backups. -----*/
3999 avg = avg_backup;
4000 is_null = is_null_backup;
4001
4002 /*----- Add global aggregate to result env. to access it in other function. -----*/
4003 if constexpr (L == 1) { // scalar
4004 Doublex1 value = avg_backup;
4005 Boolx1 is_null = is_null_backup;
4006 results.add(info.entry.id, Select(is_null, _Doublex1::Null(), value));
4007 } else { // vectorial
4008 /* Create lambda which emits the computation of the final *scalar* aggregate.
4009 * This can then be called in the pipeline function starting at the aggregation
4010 * operator s.t. the emitted variable is a local of the correct function.
4011 * Do not access the global variables inside the lambda using closure by
4012 * reference since they are already destroyed when the lambda will be called.
4013 * Instead, copy their values into the lambda. However, since DSL expressions
4014 * are not const-copy-constructible, we have to allocate them on the heap and
4015 * destroy them manually inside the lambda. */
4016 auto simd_avg = new Double<L>(avg_backup.val());
4017 auto simd_is_null = new Bool<L>(is_null_backup.val());
4018 auto simd_running_count = new I64<L>([&](){
4019 auto it = avg_aggregates.find(info.entry.id);
4020 M_insist(it != avg_aggregates.end());
4021 const auto &avg_info = it->second;
4022 M_insist(avg_info.compute_running_avg,
4023 "AVG aggregate may only occur for running average computations");
4024
4025 auto running_count_idx = std::distance(
4026 aggregates.cbegin(),
4027 std::find_if(
4028 aggregates.cbegin(), aggregates.cend(), [&avg_info](const auto &info){
4029 return info.entry.id == avg_info.running_count;
4030 })
4031 );
4032 M_insist(0 <= running_count_idx and running_count_idx < aggregates.size());
4033
4034 auto &running_count =
4035 *M_notnull(std::get_if<Global<I64<L>>>(&agg_value_backups[running_count_idx]));
4036 return running_count.val();
4037 }());
4038 finalize_aggregates.emplace_back([&, simd_avg, simd_is_null, simd_running_count]() {
4039 Doublex1 value = [&]<std::size_t... Is>(std::index_sequence<Is...>) {
4040 I64x1 count = (simd_running_count->clone().template extract<Is>() + ...);
4041 const Var<Double<L>> simd_sum([&](){
4042 if constexpr (L != 2) {
4043 return *simd_avg * simd_running_count->template to<double>();
4044 } else {
4045 M_unreachable("conversion from `I64<2>` to `Double<2>` not supported");
4046 return Double<L>(0.0); // this line is never reached; return dummy value
4047 }
4048 }());
4049 return (simd_sum.template extract<Is>() + ...) / count.to<double>();
4050 }(std::make_index_sequence<L>{});
4051 Boolx1 is_null = simd_is_null->all_true();
4052 results.add(info.entry.id, Select(is_null, _Doublex1::Null(), value));
4053 delete simd_avg; // destroy heap-allocated variable
4054 delete simd_is_null; // destroy heap-allocated variable
4055 delete simd_running_count; // destroy heap-allocated variable
4056 });
4057 }
4058
4059 /*----- Move aggregate variables to access them later. ----*/
4060 new (&agg_values[idx]) agg_t(std::make_pair(std::move(avg), std::move(is_null)));
4061 new (&agg_value_backups[idx]) agg_backup_t(std::make_pair(
4062 std::move(avg_backup), std::move(is_null_backup)
4063 ));
4064 }
4065 }
4066 };
4067 switch (CodeGenContext::Get().num_simd_lanes()) {
4068 default: M_unreachable("unsupported number of SIMD lanes");
4069 case 1: execute_setup.operator()<1>(); break;
4070 case 2: execute_setup.operator()<2>(); break;
4071 case 4: execute_setup.operator()<4>(); break;
4072 case 8: execute_setup.operator()<8>(); break;
4073 case 16: execute_setup.operator()<16>(); break;
4074 case 32: execute_setup.operator()<32>(); break;
4075 }
4076 }),
4077 /* pipeline= */ [&](){
4078 auto execute_pipeline = [&]<std::size_t L>(){
4079#ifndef NDEBUG
4081 "number of SIMD lanes in pipeline callback must match the one in setup callback");
4082#endif
4083
4084 /*----- Get aggregates helper structures. -----*/
4085 using agg_t = agg_t_<false, L>;
4086 using agg_backup_t = agg_t_<true, L>;
4087 auto agg_values = static_cast<agg_t*>(_agg_values);
4088 auto agg_value_backups = static_cast<agg_backup_t*>(_agg_value_backups);
4089
4090 auto &env = CodeGenContext::Get().env();
4091
4092 /*----- If predication is used, introduce pred. var. and update it before computing aggregates. --*/
4093 std::optional<Var<Bool<L>>> pred;
4094 if (env.predicated()) {
4095 if constexpr (sql_boolean_type<_Bool<L>>)
4096 pred = env.extract_predicate<_Bool<L>>().is_true_and_not_null();
4097 else
4098 M_unreachable("invalid number of SIMD lanes");
4099 }
4100
4101 /*----- Compute aggregates (except AVG). -----*/
4102 for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
4103 auto &info = aggregates[idx];
4104
4105 bool is_min = false;
4106 switch (info.fnid) {
4107 default:
4108 M_unreachable("unsupported aggregate function");
4109 case m::Function::FN_MIN:
4110 is_min = true; // set flag and delegate to MAX case
4111 case m::Function::FN_MAX: {
4112 M_insist(info.args.size() == 1,
4113 "MIN and MAX aggregate functions expect exactly one argument");
4114 const auto &arg = *info.args[0];
4115 auto min_max = overloaded{
4116 [&]<typename T>() requires sql_type<Expr<T, L>> {
4117 auto &[min_max, is_null] = *M_notnull((
4118 std::get_if<
4119 std::pair<Var<PrimitiveExpr<T, L>>, Var<Bool<L>>>
4120 >(&agg_values[idx])
4121 ));
4122
4123 auto _arg = env.compile(arg);
4124 Expr<T, L> _new_val = convert<Expr<T, L>>(_arg);
4125 if (_new_val.can_be_null()) {
4127 auto _new_val_pred =
4128 pred ? Select(*pred, _new_val, Expr<T, L>::Null()) : _new_val;
4129 auto [new_val_, new_val_is_null_] = _new_val_pred.split();
4130 const Var<Bool<L>> new_val_is_null(new_val_is_null_); // due to multiple uses
4131
4132 if constexpr (requires (PrimitiveExpr<T, L> v) { min(v, v); max(v, v); }) {
4133 min_max = Select(new_val_is_null,
4134 min_max, // ignore NULL
4135 is_min ? min(min_max, new_val_) // update old min with new value
4136 : max(min_max, new_val_)); // update old max with new value
4137 } else {
4138 const Var<PrimitiveExpr<T, L>> new_val(new_val_); // due to multiple uses
4139 auto cmp = is_min ? new_val < min_max : new_val > min_max;
4140#if 1
4141 min_max = Select(new_val_is_null,
4142 min_max, // ignore NULL
4143 Select(cmp,
4144 new_val, // update to new value
4145 min_max)); // do not update
4146#else
4147 IF (not new_val_is_null and cmp) {
4148 min_max = new_val;
4149 };
4150#endif
4151 }
4152 is_null = is_null and new_val_is_null; // MIN/MAX is NULL iff all values are NULL
4153 } else {
4154 auto neutral = is_min ? std::numeric_limits<T>::max()
4155 : std::numeric_limits<T>::lowest();
4156 auto _new_val_pred =
4157 pred ? Select(*pred, _new_val, PrimitiveExpr<T, L>(neutral)) : _new_val;
4158 auto new_val_ = _new_val_pred.insist_not_null();
4159 if constexpr (requires (PrimitiveExpr<T, L> v) { min(v, v); max(v, v); }) {
4160 min_max = is_min ? min(min_max, new_val_) // update old min with new value
4161 : max(min_max, new_val_); // update old max with new value
4162 } else {
4163 const Var<PrimitiveExpr<T, L>> new_val(new_val_); // due to multiple uses
4164 auto cmp = is_min ? new_val < min_max : new_val > min_max;
4165#if 1
4166 min_max = Select(cmp,
4167 new_val, // update to new value
4168 min_max); // do not update
4169#else
4170 IF (cmp) {
4171 min_max = new_val;
4172 };
4173#endif
4174 }
4175 is_null.set_false(); // at least one non-NULL value is consumed
4176 }
4177 },
4178 []<typename>() { M_unreachable("invalid type for given number of SIMD lanes"); }
4179 };
4180 auto &n = as<const Numeric>(*info.entry.type);
4181 switch (n.kind) {
4182 case Numeric::N_Int:
4183 case Numeric::N_Decimal:
4184 switch (n.size()) {
4185 default: M_unreachable("invalid size");
4186 case 8: min_max.template operator()<int8_t >(); break;
4187 case 16: min_max.template operator()<int16_t>(); break;
4188 case 32: min_max.template operator()<int32_t>(); break;
4189 case 64: min_max.template operator()<int64_t>(); break;
4190 }
4191 break;
4192 case Numeric::N_Float:
4193 if (n.size() <= 32)
4194 min_max.template operator()<float>();
4195 else
4196 min_max.template operator()<double>();
4197 }
4198 break;
4199 }
4200 case m::Function::FN_AVG:
4201 break; // skip here and handle later
4202 case m::Function::FN_SUM: {
4203 M_insist(info.args.size() == 1, "SUM aggregate function expects exactly one argument");
4204 const auto &arg = *info.args[0];
4205
4206 auto sum = overloaded{
4207 [&]<typename T>() requires sql_type<Expr<T, L>> {
4208 auto &[sum, is_null] = *M_notnull((
4209 std::get_if<
4210 std::pair<Var<PrimitiveExpr<T, L>>, Var<Bool<L>>>
4211 >(&agg_values[idx])
4212 ));
4213
4214 auto _arg = env.compile(arg);
4215 Expr<T, L> _new_val = convert<Expr<T, L>>(_arg);
4216 if (_new_val.can_be_null()) {
4218 auto _new_val_pred =
4219 pred ? Select(*pred, _new_val, Expr<T, L>::Null()) : _new_val;
4220 auto [new_val, new_val_is_null_] = _new_val_pred.split();
4221 const Var<Bool<L>> new_val_is_null(new_val_is_null_); // due to multiple uses
4222
4223 sum += Select(new_val_is_null,
4224 PrimitiveExpr<T, L>(T(0)), // ignore NULL
4225 new_val); // add new value to old sum
4226 is_null = is_null and new_val_is_null; // SUM is NULL iff all values are NULL
4227 } else {
4228 auto _new_val_pred =
4229 pred ? Select(*pred, _new_val, PrimitiveExpr<T, L>(T(0))) : _new_val;
4230 sum += _new_val_pred.insist_not_null(); // add new value to old sum
4231 is_null.set_false(); // at least one non-NULL value is consumed
4232 }
4233 },
4234 []<typename>() { M_unreachable("invalid type for given number of SIMD lanes"); }
4235 };
4236 auto &n = as<const Numeric>(*info.entry.type);
4237 switch (n.kind) {
4238 case Numeric::N_Int:
4239 case Numeric::N_Decimal:
4240 switch (n.size()) {
4241 default: M_unreachable("invalid size");
4242 case 8: sum.template operator()<int8_t >(); break;
4243 case 16: sum.template operator()<int16_t>(); break;
4244 case 32: sum.template operator()<int32_t>(); break;
4245 case 64: sum.template operator()<int64_t>(); break;
4246 }
4247 break;
4248 case Numeric::N_Float:
4249 if (n.size() <= 32)
4250 sum.template operator()<float>();
4251 else
4252 sum.template operator()<double>();
4253 }
4254 break;
4255 }
4256 case m::Function::FN_COUNT: {
4257 M_insist(info.args.size() <= 1, "COUNT aggregate function expects at most one argument");
4258 M_insist(info.entry.type->is_integral() and info.entry.type->size() == 64);
4259
4260 auto &count = *M_notnull(std::get_if<Var<I64<L>>>(&agg_values[idx]));
4261
4262 if (info.args.empty()) {
4263 count += pred ? pred->template to<int64_t>() : I64<L>(1); // increment old count by 1 iff `pred` is true
4264 } else {
4265 auto _new_val = env.compile(*info.args[0]);
4266 if (can_be_null(_new_val)) {
4268 I64<L> inc = pred ? (not_null<L>(_new_val) and *pred).template to<int64_t>()
4269 : not_null<L>(_new_val).template to<int64_t>();
4270 count += inc; // increment old count by 1 iff new value is present and `pred` is true
4271 } else {
4272 discard(_new_val); // since it is not needed in this case
4273 I64<L> inc = pred ? pred->template to<int64_t>() : I64<L>(1);
4274 count += inc; // increment old count by 1 iff new value is present and `pred` is true
4275 }
4276 }
4277 break;
4278 }
4279 }
4280 }
4281
4282 /*----- Compute AVG aggregates after others to ensure that running count is incremented before. --*/
4283 for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
4284 auto &info = aggregates[idx];
4285
4286 if (info.fnid == m::Function::FN_AVG) {
4287 M_insist(info.args.size() == 1, "AVG aggregate function expects exactly one argument");
4288 const auto &arg = *info.args[0];
4289 M_insist(info.entry.type->is_double());
4290
4291 auto it = avg_aggregates.find(info.entry.id);
4292 M_insist(it != avg_aggregates.end());
4293 const auto &avg_info = it->second;
4294 M_insist(avg_info.compute_running_avg,
4295 "AVG aggregate may only occur for running average computations");
4296
4297 auto &[avg, is_null] = *M_notnull((
4298 std::get_if<std::pair<Var<Double<L>>, Var<Bool<L>>>>(&agg_values[idx])
4299 ));
4300
4301 /* Compute AVG as iterative mean as described in Knuth, The Art of Computer Programming
4302 * Vol 2, section 4.2.2. */
4303 auto running_count_idx = std::distance(
4304 aggregates.cbegin(),
4305 std::find_if(aggregates.cbegin(), aggregates.cend(), [&avg_info](const auto &info){
4306 return info.entry.id == avg_info.running_count;
4307 })
4308 );
4309 M_insist(0 <= running_count_idx and running_count_idx < aggregates.size());
4310 Double<L> running_count = [&](){
4311 auto &running_count =
4312 *M_notnull(std::get_if<Var<I64<L>>>(&agg_values[running_count_idx]));
4313 if constexpr (L != 2) {
4314 return running_count.template to<double>();
4315 } else {
4316 M_unreachable("conversion from `I64<2>` to `Double<2>` not supported");
4317 return Double<L>(0.0); // this line is never reached; return dummy value
4318 }
4319 }();
4320
4321 auto _arg = env.compile(arg);
4322 _Double<L> _new_val = convert<_Double<L>>(_arg);
4323 if (_new_val.can_be_null()) {
4325 auto _new_val_pred = pred ? Select(*pred, _new_val, _Double<L>::Null()) : _new_val;
4326 auto [new_val, new_val_is_null_] = _new_val_pred.split();
4327 const Var<Bool<L>> new_val_is_null(new_val_is_null_); // due to multiple uses
4328
4329 auto delta_absolute = new_val - avg;
4330 auto delta_relative = delta_absolute / running_count;
4331
4332 avg += Select(new_val_is_null,
4333 Double<L>(0.0), // ignore NULL
4334 delta_relative); // update old average with new value
4335 is_null = is_null and new_val_is_null; // AVG is NULL iff all values are NULL
4336 } else {
4337 auto _new_val_pred = pred ? Select(*pred, _new_val, avg) : _new_val;
4338 auto delta_absolute = _new_val_pred.insist_not_null() - avg;
4339 auto delta_relative = delta_absolute / running_count;
4340
4341 avg += delta_relative; // update old average with new value
4342 is_null.set_false(); // at least one non-NULL value is consumed
4343 }
4344 }
4345 }
4346 };
4347 switch (CodeGenContext::Get().num_simd_lanes()) {
4348 default: M_unreachable("unsupported number of SIMD lanes");
4349 case 1: execute_pipeline.operator()<1>(); break;
4350 case 2: execute_pipeline.operator()<2>(); break;
4351 case 4: execute_pipeline.operator()<4>(); break;
4352 case 8: execute_pipeline.operator()<8>(); break;
4353 case 16: execute_pipeline.operator()<16>(); break;
4354 case 32: execute_pipeline.operator()<32>(); break;
4355 }
4356 },
4357 /* teardown= */ teardown_t::Make_Without_Parent([&](){
4358 auto execute_teardown = [&]<std::size_t L>(){
4359#ifndef NDEBUG
4361 "number of SIMD lanes in teardown callback must match the one in setup callback");
4362#endif
4363
4364 /*----- Get aggregates helper structures. -----*/
4365 using agg_t = agg_t_<false, L>;
4366 using agg_backup_t = agg_t_<true, L>;
4367 auto agg_values = static_cast<agg_t*>(_agg_values);
4368 auto agg_value_backups = static_cast<agg_backup_t*>(_agg_value_backups);
4369
4370 /*----- Store local aggregate values to globals to access them in other function. -----*/
4371 for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
4372 auto &info = aggregates[idx];
4373
4374 bool is_min = false;
4375 switch (info.fnid) {
4376 default:
4377 M_unreachable("unsupported aggregate function");
4378 case m::Function::FN_MIN:
4379 is_min = true; // set flag and delegate to MAX case
4380 case m::Function::FN_MAX: {
4381 auto min_max = [&]<typename T>() {
4382 auto &[min_max_backup, is_null_backup] = *M_notnull((
4383 std::get_if<
4384 std::pair<Global<PrimitiveExpr<T, L>>, Global<Bool<L>>>
4385 >(&agg_value_backups[idx])
4386 ));
4387 std::tie(min_max_backup, is_null_backup) = *M_notnull((
4388 std::get_if<std::pair<Var<PrimitiveExpr<T, L>>, Var<Bool<L>>>>(&agg_values[idx])
4389 ));
4390 };
4391 auto &n = as<const Numeric>(*info.entry.type);
4392 switch (n.kind) {
4393 case Numeric::N_Int:
4394 case Numeric::N_Decimal:
4395 switch (n.size()) {
4396 default: M_unreachable("invalid size");
4397 case 8: min_max.template operator()<int8_t >(); break;
4398 case 16: min_max.template operator()<int16_t>(); break;
4399 case 32: min_max.template operator()<int32_t>(); break;
4400 case 64: min_max.template operator()<int64_t>(); break;
4401 }
4402 break;
4403 case Numeric::N_Float:
4404 if (n.size() <= 32)
4405 min_max.template operator()<float>();
4406 else
4407 min_max.template operator()<double>();
4408 }
4409 break;
4410 }
4411 case m::Function::FN_AVG: {
4412 auto &[avg_backup, is_null_backup] = *M_notnull((
4413 std::get_if<std::pair<Global<Double<L>>, Global<Bool<L>>>>(&agg_value_backups[idx])
4414 ));
4415 std::tie(avg_backup, is_null_backup) = *M_notnull((
4416 std::get_if<std::pair<Var<Double<L>>, Var<Bool<L>>>>(&agg_values[idx])
4417 ));
4418
4419 break;
4420 }
4421 case m::Function::FN_SUM: {
4422 M_insist(info.args.size() == 1, "SUM aggregate function expects exactly one argument");
4423 const auto &arg = *info.args[0];
4424
4425 auto sum = [&]<typename T>() {
4426 auto &[sum_backup, is_null_backup] = *M_notnull((
4427 std::get_if<
4428 std::pair<Global<PrimitiveExpr<T, L>>, Global<Bool<L>>>
4429 >(&agg_value_backups[idx])
4430 ));
4431 std::tie(sum_backup, is_null_backup) = *M_notnull((
4432 std::get_if<std::pair<Var<PrimitiveExpr<T, L>>, Var<Bool<L>>>>(&agg_values[idx])
4433 ));
4434 };
4435 auto &n = as<const Numeric>(*info.entry.type);
4436 switch (n.kind) {
4437 case Numeric::N_Int:
4438 case Numeric::N_Decimal:
4439 switch (n.size()) {
4440 default: M_unreachable("invalid size");
4441 case 8: sum.template operator()<int8_t >(); break;
4442 case 16: sum.template operator()<int16_t>(); break;
4443 case 32: sum.template operator()<int32_t>(); break;
4444 case 64: sum.template operator()<int64_t>(); break;
4445 }
4446 break;
4447 case Numeric::N_Float:
4448 if (n.size() <= 32)
4449 sum.template operator()<float>();
4450 else
4451 sum.template operator()<double>();
4452 }
4453 break;
4454 }
4455 case m::Function::FN_COUNT: {
4456 auto &count_backup = *M_notnull(std::get_if<Global<I64<L>>>(&agg_value_backups[idx]));
4457 count_backup = *M_notnull(std::get_if<Var<I64<L>>>(&agg_values[idx]));
4458
4459 break;
4460 }
4461 }
4462 }
4463
4464 /*----- Destroy created aggregates and their backups. -----*/
4465 for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
4466 agg_values[idx].~agg_t();
4467 agg_value_backups[idx].~agg_backup_t();
4468 }
4469
4470 /*----- Free aggregates helper structures. -----*/
4471 delete[] agg_values;
4472 delete[] agg_value_backups;
4473 };
4474 switch (CodeGenContext::Get().num_simd_lanes()) {
4475 default: M_unreachable("unsupported number of SIMD lanes");
4476 case 1: execute_teardown.operator()<1>(); break;
4477 case 2: execute_teardown.operator()<2>(); break;
4478 case 4: execute_teardown.operator()<4>(); break;
4479 case 8: execute_teardown.operator()<8>(); break;
4480 case 16: execute_teardown.operator()<16>(); break;
4481 case 32: execute_teardown.operator()<32>(); break;
4482 }
4483 })
4484 );
4485 }
4486 aggregation_child_pipeline(); // call child function
4487
4488 /*----- Emit setup code *before* possibly introducing temporary boolean variables to not overwrite them. -----*/
4489 setup();
4490
4491 /*----- Emit code to finalize aggregate computations. -----*/
4492 for (auto &fn : finalize_aggregates)
4493 fn();
4494
4495 /*----- Add computed aggregates tuple to current environment. ----*/
4496 auto &env = CodeGenContext::Get().env();
4497 for (auto &e : M.aggregation.schema().deduplicate()) {
4498 if (auto it = avg_aggregates.find(e.id);
4499 it != avg_aggregates.end() and not it->second.compute_running_avg)
4500 { // AVG aggregates which is not yet computed, divide computed sum with computed count
4501 auto &avg_info = it->second;
4502 auto sum = results.get(avg_info.sum);
4503 auto count = results.get<_I64x1>(avg_info.running_count).insist_not_null().to<double>();
4504 auto avg = convert<_Doublex1>(sum) / count;
4505 M_insist(avg.can_be_null());
4506 _Var<Doublex1> var(avg); // introduce variable s.t. uses only load from it
4507 env.add(e.id, var);
4508 } else { // already computed aggregate
4509 std::visit(overloaded {
4510 [&]<typename T>(Expr<T> value) -> void {
4511 if (value.can_be_null()) {
4512 Var<Expr<T>> var(value); // introduce variable s.t. uses only load from it
4513 env.add(e.id, var);
4514 } else {
4515 /* introduce variable w/o NULL bit s.t. uses only load from it */
4516 Var<PrimitiveExpr<T>> var(value.insist_not_null());
4517 env.add(e.id, Expr<T>(var));
4518 }
4519 },
4520 [](auto) -> void { M_unreachable("only scalar and non-string values must occur"); },
4521 [](std::monostate) -> void { M_unreachable("invalid reference"); },
4522 }, results.get(e.id)); // do not extract to be able to access for not-yet-computed AVG aggregates
4523 }
4524 }
4525
4526 /*----- Resume pipeline. -----*/
4527 CodeGenContext::Get().set_num_simd_lanes(1); // since only a single tuple is produced
4528 pipeline();
4529
4530 /*----- Emit teardown code. -----*/
4531 teardown();
4532}
4533
4534
4535/*======================================================================================================================
4536 * Sorting
4537 *====================================================================================================================*/
4538
4539template<bool CmpPredicated>
4540ConditionSet Quicksort<CmpPredicated>::pre_condition(std::size_t child_idx, const std::tuple<const SortingOperator*>&)
4541{
4542 M_insist(child_idx == 0);
4543
4544 ConditionSet pre_cond;
4545
4546 /*----- Sorting does not support SIMD. -----*/
4547 pre_cond.add_condition(NoSIMD());
4548
4549 return pre_cond;
4550}
4551
4552template<bool CmpPredicated>
4554{
4555 ConditionSet post_cond;
4556
4557 /*----- Quicksort does not introduce predication. -----*/
4558 post_cond.add_condition(Predicated(false));
4559
4560 /*----- Quicksort does sort the data. -----*/
4561 Sortedness::order_t orders;
4562 for (auto &o : M.sorting.order_by()) {
4563 Schema::Identifier id(o.first);
4564 if (orders.find(id) == orders.cend())
4565 orders.add(std::move(id), o.second ? Sortedness::O_ASC : Sortedness::O_DESC);
4566 }
4567 post_cond.add_condition(Sortedness(std::move(orders)));
4568
4569 /*----- Sorting does not introduce SIMD. -----*/
4570 post_cond.add_condition(NoSIMD());
4571
4572 return post_cond;
4573}
4574
4575template<bool CmpPredicated>
4577 teardown_t teardown)
4578{
4579 /*----- Create infinite buffer to materialize the current results but resume the pipeline later. -----*/
4580 M_insist(bool(M.materializing_factory), "`wasm::Quicksort` must have a factory for the materialized child");
4581 const auto buffer_schema = M.child->get_matched_root().schema().drop_constants().deduplicate();
4582 const auto sorting_schema = M.sorting.schema().drop_constants().deduplicate();
4583 GlobalBuffer buffer(
4584 buffer_schema, *M.materializing_factory, false, 0, std::move(setup), std::move(pipeline), std::move(teardown)
4585 );
4586
4587 /*----- Create child function. -----*/
4588 FUNCTION(sorting_child_pipeline, void(void)) // create function for pipeline
4589 {
4590 auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
4591
4592 M.child->execute(
4593 /* setup= */ setup_t::Make_Without_Parent([&](){ buffer.setup(); }),
4594 /* pipeline= */ [&](){ buffer.consume(); },
4595 /* teardown= */ teardown_t::Make_Without_Parent([&](){ buffer.teardown(); })
4596 );
4597 }
4598 sorting_child_pipeline(); // call child function
4599
4600 /*----- Invoke quicksort algorithm with buffer to sort. -----*/
4601 quicksort<CmpPredicated>(buffer, M.sorting.order_by());
4602
4603 /*----- Process sorted buffer. -----*/
4604 buffer.resume_pipeline(sorting_schema);
4605}
4606
4608 const std::tuple<const SortingOperator*> &partial_inner_nodes)
4609{
4610 M_insist(child_idx == 0);
4611
4612 ConditionSet pre_cond;
4613
4614 /*----- NoOpSorting, i.e. a noop to match sorting, needs the data already sorted. -----*/
4615 Sortedness::order_t orders;
4616 for (auto &o : std::get<0>(partial_inner_nodes)->order_by()) {
4617 Schema::Identifier id(o.first);
4618 if (orders.find(id) == orders.cend())
4619 orders.add(std::move(id), o.second ? Sortedness::O_ASC : Sortedness::O_DESC);
4620 }
4621 pre_cond.add_condition(Sortedness(std::move(orders)));
4622
4623 return pre_cond;
4624}
4625
4627{
4628 M.child->execute(std::move(setup), std::move(pipeline), std::move(teardown));
4629}
4630
4631
4632/*======================================================================================================================
4633 * Join
4634 *====================================================================================================================*/
4635
4636template<bool Predicated>
4637ConditionSet NestedLoopsJoin<Predicated>::pre_condition(std::size_t, const std::tuple<const JoinOperator*>&)
4638{
4639 ConditionSet pre_cond;
4640
4641 /*----- Nested-loops join does not support SIMD. -----*/
4642 pre_cond.add_condition(NoSIMD());
4643
4644 return pre_cond;
4645}
4646
4647template<bool Predicated>
4650 std::vector<std::reference_wrapper<const ConditionSet>> &&post_cond_children)
4651{
4652 M_insist(post_cond_children.size() >= 2);
4653
4654 ConditionSet post_cond(post_cond_children.back().get()); // preserve conditions of right-most child
4655
4656 if constexpr (Predicated) {
4657 /*----- Predicated nested-loops join introduces predication. -----*/
4658 post_cond.add_or_replace_condition(m::Predicated(true));
4659 }
4660
4661 return post_cond;
4662}
4663
4664template<bool Predicated>
4666{
4667 double cost = 1;
4668 for (auto &child : M.children)
4669 cost *= child->get_matched_root().info().estimated_cardinality;
4670 return cost;
4671}
4672
4673template<bool Predicated>
4675 teardown_t teardown)
4676{
4677 const auto num_left_children = M.children.size() - 1; // all children but right-most one
4678
4679 std::vector<Schema> schemas; // to own adapted schemas
4680 schemas.reserve(num_left_children);
4681 std::vector<GlobalBuffer> buffers;
4682 buffers.reserve(num_left_children);
4683
4684 /*----- Process all but right-most child. -----*/
4685 for (std::size_t i = 0; i < num_left_children; ++i) {
4686 /*----- Create function for each child. -----*/
4687 FUNCTION(nested_loop_join_child_pipeline, void(void))
4688 {
4689 auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
4690
4691 /*----- Create infinite buffer to materialize the current results. -----*/
4692 M_insist(bool(M.materializing_factories_[i]),
4693 "`wasm::NestedLoopsJoin` must have a factory for each materialized child");
4694 const auto &schema = schemas.emplace_back(
4695 M.children[i]->get_matched_root().schema().drop_constants().deduplicate()
4696 );
4697 if (i == 0) {
4698 /*----- Exactly one child (here left-most one) checks join predicate and resumes pipeline. -----*/
4699 buffers.emplace_back(
4700 /* schema= */ schema,
4701 /* factory= */ *M.materializing_factories_[i],
4702 /* load_simdfied= */ false,
4703 /* num_tuples= */ 0, // i.e. infinite
4704 /* setup= */ setup_t::Make_Without_Parent(),
4705 /* pipeline= */ [&, pipeline=std::move(pipeline)](){
4706 if constexpr (Predicated) {
4707 CodeGenContext::Get().env().add_predicate(M.join.predicate());
4708 pipeline();
4709 } else {
4710 M_insist(CodeGenContext::Get().num_simd_lanes() == 1, "invalid number of SIMD lanes");
4711 IF (CodeGenContext::Get().env().compile<_Boolx1>(M.join.predicate()).is_true_and_not_null()) {
4712 pipeline();
4713 };
4714 }
4715 },
4716 /* teardown= */ teardown_t::Make_Without_Parent()
4717 );
4718 } else {
4719 /*----- All but exactly one child (here left-most one) load lastly inserted buffer again. -----*/
4720 /* All buffers are "connected" with each other by setting the pipeline callback as calling the
4721 * `resume_pipeline_inline()` method of the lastly inserted buffer. Therefore, calling
4722 * `resume_pipeline_inline()` on the lastly inserted buffer will load one tuple from it, recursively
4723 * call `resume_pipeline_inline()` on the buffer created before that which again loads one tuple from
4724 * it, and so on until the buffer inserted first (here the one of the left-most child) will load one
4725 * of its tuples and check the join predicate for this one cartesian-product-combination of result
4726 * tuples. */
4727 buffers.emplace_back(
4728 /* schema= */ schema,
4729 /* factory= */ *M.materializing_factories_[i],
4730 /* load_simdfied= */ false,
4731 /* num_tuples= */ 0, // i.e. infinite
4732 /* setup= */ setup_t::Make_Without_Parent(),
4733 /* pipeline= */ [&](){ buffers.back().resume_pipeline_inline(); },
4734 /* teardown= */ teardown_t::Make_Without_Parent()
4735 );
4736 }
4737
4738 /*----- Materialize the current result tuple in pipeline. -----*/
4739 M.children[i]->execute(
4740 /* setup= */ setup_t::Make_Without_Parent([&](){ buffers.back().setup(); }),
4741 /* pipeline= */ [&](){ buffers.back().consume(); },
4742 /* teardown= */ teardown_t::Make_Without_Parent([&](){ buffers.back().teardown(); })
4743 );
4744 }
4745 nested_loop_join_child_pipeline(); // call child function
4746 }
4747
4748 /*----- Process right-most child. -----*/
4749 M.children.back()->execute(
4750 /* setup= */ std::move(setup),
4751 /* pipeline= */ [&](){ buffers.back().resume_pipeline_inline(); },
4752 /* teardown= */ std::move(teardown)
4753 );
4754}
4755
4756template<bool UniqueBuild, bool Predicated>
4758 std::size_t,
4759 const std::tuple<const JoinOperator*, const Wildcard*, const Wildcard*> &partial_inner_nodes)
4760{
4761 ConditionSet pre_cond;
4762
4763 /*----- Simple hash join can only be used for binary joins on equi-predicates. -----*/
4764 auto &join = *std::get<0>(partial_inner_nodes);
4765 if (not join.predicate().is_equi())
4767
4768 if constexpr (UniqueBuild) {
4769 /*----- Decompose each clause of the join predicate of the form `A.x = B.y` into parts `A.x` and `B.y`. -----*/
4770 auto &build = *std::get<1>(partial_inner_nodes);
4771 for (auto &clause : join.predicate()) {
4772 M_insist(clause.size() == 1, "invalid equi-predicate");
4773 auto &literal = clause[0];
4774 auto &binary = as<const BinaryExpr>(literal.expr());
4775 M_insist((not literal.negative() and binary.tok == TK_EQUAL) or
4776 (literal.negative() and binary.tok == TK_BANG_EQUAL), "invalid equi-predicate");
4777 M_insist(is<const Designator>(binary.lhs), "invalid equi-predicate");
4778 M_insist(is<const Designator>(binary.rhs), "invalid equi-predicate");
4779 Schema::Identifier id_first(*binary.lhs), id_second(*binary.rhs);
4780 const auto &entry_build = build.schema().has(id_first) ? build.schema()[id_first].second
4781 : build.schema()[id_second].second;
4782
4783 /*----- Unique simple hash join can only be used on unique build key. -----*/
4784 if (not entry_build.unique())
4786 }
4787 }
4788
4789 /*----- Simple hash join does not support SIMD. -----*/
4790 pre_cond.add_condition(NoSIMD());
4791
4792 return pre_cond;
4793}
4794
4795template<bool UniqueBuild, bool Predicated>
4797 const Match<SimpleHashJoin>&,
4798 std::vector<std::reference_wrapper<const ConditionSet>> &&post_cond_children)
4799{
4800 M_insist(post_cond_children.size() == 2);
4801
4802 ConditionSet post_cond(post_cond_children[1].get()); // preserve conditions of right child
4803
4804 if constexpr (Predicated) {
4805 /*----- Predicated simple hash join introduces predication. -----*/
4806 post_cond.add_or_replace_condition(m::Predicated(true));
4807 } else {
4808 /*----- Branching simple hash join does not introduce predication (it is already handled by the hash table). -*/
4809 post_cond.add_or_replace_condition(m::Predicated(false));
4810 }
4811
4812 return post_cond;
4813}
4814
4815template<bool UniqueBuild, bool Predicated>
4817{
4819 return (M.build.id() == M.children[0]->get_matched_root().id() ? 1.0 : 2.0) + (UniqueBuild ? 0.0 : 0.1);
4821 return M.build.id() == M.children[1]->get_matched_root().id() ? 1.0 : 2.0 + (UniqueBuild ? 0.0 : 0.1);
4822 else
4823 return 1.5 * M.build.info().estimated_cardinality +
4824 (UniqueBuild ? 1.0 : 1.1) * M.probe.info().estimated_cardinality;
4825}
4826
4827template<bool UniqueBuild, bool Predicated>
4829 pipeline_t pipeline, teardown_t teardown)
4830{
4831 // TODO: determine setup
4832 const uint64_t PAYLOAD_SIZE_THRESHOLD_IN_BITS =
4833 M.use_in_place_values ? std::numeric_limits<uint64_t>::max() : 0;
4834
4835 M_insist(((M.join.schema() | M.join.predicate().get_required()) & M.build.schema()) == M.build.schema());
4836 M_insist(M.build.schema().drop_constants() == M.build.schema());
4837 const auto ht_schema = M.build.schema().deduplicate();
4838
4839 /*----- Decompose each clause of the join predicate of the form `A.x = B.y` into parts `A.x` and `B.y`. -----*/
4840 const auto [build_keys, probe_keys] = decompose_equi_predicate(M.join.predicate(), ht_schema);
4841
4842 /*----- Compute payload IDs and its total size in bits (ignoring padding). -----*/
4843 std::vector<Schema::Identifier> payload_ids;
4844 uint64_t payload_size_in_bits = 0;
4845 for (auto &e : ht_schema) {
4846 if (not contains(build_keys, e.id)) {
4847 payload_ids.push_back(e.id);
4848 payload_size_in_bits += e.type->size();
4849 }
4850 }
4851
4852 /*----- Compute initial capacity of hash table. -----*/
4853 uint32_t initial_capacity = compute_initial_ht_capacity(M.build, M.load_factor);
4854
4855 /*----- Create hash table for build child. -----*/
4856 std::unique_ptr<HashTable> ht;
4857 std::vector<HashTable::index_t> build_key_indices;
4858 for (auto &build_key : build_keys)
4859 build_key_indices.push_back(ht_schema[build_key].first);
4860 if (M.use_open_addressing_hashing) {
4861 if (payload_size_in_bits < PAYLOAD_SIZE_THRESHOLD_IN_BITS)
4862 ht = std::make_unique<GlobalOpenAddressingInPlaceHashTable>(ht_schema, std::move(build_key_indices),
4863 initial_capacity);
4864 else
4865 ht = std::make_unique<GlobalOpenAddressingOutOfPlaceHashTable>(ht_schema, std::move(build_key_indices),
4866 initial_capacity);
4867 if (M.use_quadratic_probing)
4868 as<OpenAddressingHashTableBase>(*ht).set_probing_strategy<QuadraticProbing>();
4869 else
4870 as<OpenAddressingHashTableBase>(*ht).set_probing_strategy<LinearProbing>();
4871 } else {
4872 ht = std::make_unique<GlobalChainedHashTable>(ht_schema, std::move(build_key_indices), initial_capacity);
4873 }
4874
4875 /*----- Create function for build child. -----*/
4876 FUNCTION(simple_hash_join_child_pipeline, void(void)) // create function for pipeline
4877 {
4878 auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
4879
4880 M.children[0]->execute(
4881 /* setup= */ setup_t::Make_Without_Parent([&](){
4882 ht->setup();
4883 ht->set_high_watermark(M.load_factor);
4884 }),
4885 /* pipeline= */ [&](){
4886 auto &env = CodeGenContext::Get().env();
4887
4888 std::optional<Boolx1> build_key_not_null;
4889 for (auto &build_key : build_keys) {
4890 auto val = env.get(build_key);
4891 if (build_key_not_null)
4892 build_key_not_null.emplace(*build_key_not_null and not_null(val));
4893 else
4894 build_key_not_null.emplace(not_null(val));
4895 }
4896 M_insist(bool(build_key_not_null));
4897 IF (*build_key_not_null) { // TODO: predicated version
4898 /*----- Insert key. -----*/
4899 std::vector<SQL_t> key;
4900 for (auto &build_key : build_keys)
4901 key.emplace_back(env.get(build_key));
4902 auto entry = ht->emplace(std::move(key));
4903
4904 /*----- Insert payload. -----*/
4905 for (auto &id : payload_ids) {
4906 std::visit(overloaded {
4907 [&]<sql_type T>(HashTable::reference_t<T> &&r) -> void { r = env.extract<T>(id); },
4908 [](std::monostate) -> void { M_unreachable("invalid reference"); },
4909 }, entry.extract(id));
4910 }
4911 };
4912 },
4913 /* teardown= */ teardown_t::Make_Without_Parent([&](){ ht->teardown(); })
4914 );
4915 }
4916 simple_hash_join_child_pipeline(); // call child function
4917
4918 M.children[1]->execute(
4919 /* setup= */ setup_t(std::move(setup), [&](){ ht->setup(); }),
4920 /* pipeline= */ [&, pipeline=std::move(pipeline)](){
4921 auto &env = CodeGenContext::Get().env();
4922
4923 auto emit_tuple_and_resume_pipeline = [&, pipeline=std::move(pipeline)](HashTable::const_entry_t entry){
4924 /*----- Add found entry from hash table, i.e. from build child, to current environment. -----*/
4925 for (auto &e : ht_schema) {
4926 if (not entry.has(e.id)) { // entry may not contain build key in case `ht->find()` was used
4927 M_insist(contains(build_keys, e.id));
4928 M_insist(env.has(e.id), "build key must already be contained in the current environment");
4929 continue;
4930 }
4931
4932 std::visit(overloaded {
4933 [&]<typename T>(HashTable::const_reference_t<Expr<T>> &&r) -> void {
4934 Expr<T> value = r;
4935 if (value.can_be_null()) {
4936 Var<Expr<T>> var(value); // introduce variable s.t. uses only load from it
4937 env.add(e.id, var);
4938 } else {
4939 /* introduce variable w/o NULL bit s.t. uses only load from it */
4940 Var<PrimitiveExpr<T>> var(value.insist_not_null());
4941 env.add(e.id, Expr<T>(var));
4942 }
4943 },
4944 [&](HashTable::const_reference_t<NChar> &&r) -> void {
4945 NChar value(r);
4946 Var<Ptr<Charx1>> var(value.val()); // introduce variable s.t. uses only load from it
4947 env.add(e.id, NChar(var, value.can_be_null(), value.length(),
4948 value.guarantees_terminating_nul()));
4949 },
4950 [](std::monostate) -> void { M_unreachable("invalid reference"); },
4951 }, entry.extract(e.id));
4952 }
4953
4954 /*----- Resume pipeline. -----*/
4955 pipeline();
4956 };
4957
4958 /* TODO: may check for NULL on probe keys as well, branching + predicated version */
4959 /*----- Probe with probe key. -----*/
4960 std::vector<SQL_t> key;
4961 for (auto &probe_key : probe_keys)
4962 key.emplace_back(env.get(probe_key));
4963 if constexpr (UniqueBuild) {
4964 /*----- Add build key to current environment since `ht->find()` will only return the payload values. -----*/
4965 for (auto build_it = build_keys.cbegin(), probe_it = probe_keys.cbegin(); build_it != build_keys.cend();
4966 ++build_it, ++probe_it)
4967 {
4968 M_insist(probe_it != probe_keys.cend());
4969 if (not env.has(*build_it)) // skip duplicated build keys and only add first occurrence
4970 env.add(*build_it, env.get(*probe_it)); // since build and probe keys match for join partners
4971 }
4972
4973 /*----- Try to find the *single* possible join partner. -----*/
4974 auto p = ht->find(std::move(key));
4975 auto &entry = p.first;
4976 auto &found = p.second;
4977 if constexpr (Predicated) {
4978 env.add_predicate(found);
4979 emit_tuple_and_resume_pipeline(std::move(entry));
4980 } else {
4981 IF (found) {
4982 emit_tuple_and_resume_pipeline(std::move(entry));
4983 };
4984 }
4985 } else {
4986 /*----- Search for *all* join partners. -----*/
4987 ht->for_each_in_equal_range(std::move(key), std::move(emit_tuple_and_resume_pipeline), Predicated);
4988 }
4989 },
4990 /* teardown= */ teardown_t(std::move(teardown), [&](){ ht->teardown(); })
4991 );
4992}
4993
4994template<bool SortLeft, bool SortRight, bool Predicated, bool CmpPredicated>
4996 std::size_t child_idx,
4997 const std::tuple<const JoinOperator*, const Wildcard*, const Wildcard*> &partial_inner_nodes)
4998{
4999 ConditionSet pre_cond;
5000
5001 /*----- Sort merge join can only be used for binary joins on conjunctions of equi-predicates. -----*/
5002 auto &join = *std::get<0>(partial_inner_nodes);
5003 if (not join.predicate().is_equi())
5005
5006 /*----- Decompose each clause of the join predicate of the form `A.x = B.y` into parts `A.x` and `B.y`. -----*/
5007 auto parent = std::get<1>(partial_inner_nodes);
5008 auto child = std::get<2>(partial_inner_nodes);
5009 M_insist(parent);
5010 M_insist(child_idx != 1 or child);
5011 std::vector<Schema::Identifier> keys_parent, keys_child;
5012 for (auto &clause : join.predicate()) {
5013 M_insist(clause.size() == 1, "invalid equi-predicate");
5014 auto &literal = clause[0];
5015 auto &binary = as<const BinaryExpr>(literal.expr());
5016 M_insist((not literal.negative() and binary.tok == TK_EQUAL) or
5017 (literal.negative() and binary.tok == TK_BANG_EQUAL), "invalid equi-predicate");
5018 M_insist(is<const Designator>(binary.lhs), "invalid equi-predicate");
5019 M_insist(is<const Designator>(binary.rhs), "invalid equi-predicate");
5020 Schema::Identifier id_first(*binary.lhs), id_second(*binary.rhs);
5022 const auto &[entry_parent, entry_child] = parent->schema().has(id_first)
5023 ? std::make_pair(parent->schema()[id_first].second, child_idx == 1 ? child->schema()[id_second].second : std::move(dummy))
5024 : std::make_pair(parent->schema()[id_second].second, child_idx == 1 ? child->schema()[id_first].second : std::move(dummy));
5025 keys_parent.push_back(entry_parent.id);
5026 keys_child.push_back(entry_child.id);
5027
5028 /*----- Sort merge join can only be used on unique parent key. -----*/
5029 if (not entry_parent.unique())
5031 }
5032 M_insist(keys_parent.size() == keys_child.size(), "number of found IDs differ");
5033 M_insist(not keys_parent.empty(), "must find at least one ID");
5034
5035 if constexpr (not SortLeft or not SortRight) {
5036 /*----- Sort merge join without sorting needs its data sorted on the respective key. -----*/
5037 Sortedness::order_t orders;
5038 M_insist(child_idx < 2);
5039 if (not SortLeft and child_idx == 0) {
5040 for (auto &key_parent : keys_parent) {
5041 if (orders.find(key_parent) == orders.cend())
5042 orders.add(key_parent, Sortedness::O_ASC); // TODO: support different order
5043 }
5044 } else if (not SortRight and child_idx == 1) {
5045 for (auto &key_child : keys_child) {
5046 if (orders.find(key_child) == orders.cend())
5047 orders.add(key_child, Sortedness::O_ASC); // TODO: support different order
5048 }
5049 }
5050 pre_cond.add_condition(Sortedness(std::move(orders)));
5051 }
5052
5053 /*----- Sort merge join does not support SIMD. -----*/
5054 pre_cond.add_condition(NoSIMD());
5055
5056 return pre_cond;
5057}
5058
5059template<bool SortLeft, bool SortRight, bool Predicated, bool CmpPredicated>
5061 const Match<SortMergeJoin> &M,
5062 std::vector<std::reference_wrapper<const ConditionSet>> &&post_cond_children)
5063{
5064 M_insist(post_cond_children.size() == 2);
5065
5066 ConditionSet post_cond;
5067
5068 if constexpr (Predicated) {
5069 /*----- Predicated sort merge join introduces predication. -----*/
5070 post_cond.add_or_replace_condition(m::Predicated(true));
5071 }
5072
5073 /*----- Sort merge join does not introduce SIMD. -----*/
5074 post_cond.add_condition(NoSIMD());
5075
5076 Sortedness::order_t orders;
5077 if constexpr (not SortLeft) {
5078 Sortedness sorting_left(post_cond_children[0].get().get_condition<Sortedness>());
5079 orders.merge(sorting_left.orders()); // preserve sortedness of left child (including order)
5080 }
5081 if constexpr (not SortRight) {
5082 Sortedness sorting_right(post_cond_children[1].get().get_condition<Sortedness>());
5083 orders.merge(sorting_right.orders()); // preserve sortedness of right child (including order)
5084 }
5085 if constexpr (SortLeft or SortRight) {
5086 /*----- Decompose each clause of the join predicate of the form `A.x = B.y` into parts `A.x` and `B.y`. -----*/
5087 auto [keys_parent, keys_child] = decompose_equi_predicate(M.join.predicate(), M.parent.schema());
5088
5089 /*----- Sort merge join does sort the data on the respective key. -----*/
5090 if constexpr (SortLeft) {
5091 for (auto &key_parent : keys_parent) {
5092 if (orders.find(key_parent) == orders.cend())
5093 orders.add(key_parent, Sortedness::O_ASC); // add sortedness for left child
5094 }
5095 }
5096 if constexpr (SortRight) {
5097 for (auto &key_child : keys_child) {
5098 if (orders.find(key_child) == orders.cend())
5099 orders.add(key_child, Sortedness::O_ASC); // add sortedness for right child
5100 }
5101 }
5102 }
5103 post_cond.add_condition(Sortedness(std::move(orders)));
5104
5105 return post_cond;
5106}
5107
5108template<bool SortLeft, bool SortRight, bool Predicated, bool CmpPredicated>
5110{
5111 const double card_left = M.parent.info().estimated_cardinality;
5112 const double card_right = M.child.info().estimated_cardinality;
5113
5114 double cost = card_left + card_right; // cost for merge
5115 if constexpr (SortLeft)
5116 cost += std::log2(card_left) * card_left; // cost for sort left
5117 if constexpr (SortRight)
5118 cost += std::log2(card_right) * card_right; // cost for sort right
5119
5120 return cost;
5121}
5122
5123template<bool SortLeft, bool SortRight, bool Predicated, bool CmpPredicated>
5125 const Match<SortMergeJoin> &M,
5126 setup_t setup,
5127 pipeline_t pipeline,
5128 teardown_t teardown)
5129{
5130 auto &env = CodeGenContext::Get().env();
5131 const bool needs_buffer_parent = not is<const ScanOperator>(M.parent) or SortLeft;
5132 const bool needs_buffer_child = not is<const ScanOperator>(M.child) or SortRight;
5133
5134 /*----- Create infinite buffers to materialize the current results (if necessary). -----*/
5135 M_insist(bool(M.left_materializing_factory),
5136 "`wasm::SortMergeJoin` must have a factory for the materialized left child");
5137 M_insist(bool(M.right_materializing_factory),
5138 "`wasm::SortMergeJoin` must have a factory for the materialized right child");
5139 const auto schema_parent = M.parent.schema().drop_constants().deduplicate();
5140 const auto schema_child = M.child.schema().drop_constants().deduplicate();
5141 std::optional<GlobalBuffer> buffer_parent, buffer_child;
5142 if (needs_buffer_parent)
5143 buffer_parent.emplace(schema_parent, *M.left_materializing_factory);
5144 if (needs_buffer_child)
5145 buffer_child.emplace(schema_child, *M.right_materializing_factory);
5146
5147 /*----- Create child functions. -----*/
5148 if (needs_buffer_parent) {
5149 FUNCTION(sort_merge_join_parent_pipeline, void(void)) // create function for parent pipeline
5150 {
5151 auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
5152 M.children[0]->execute(
5153 /* setup= */ setup_t::Make_Without_Parent([&](){ buffer_parent->setup(); }),
5154 /* pipeline= */ [&](){ buffer_parent->consume(); },
5155 /* teardown= */ teardown_t::Make_Without_Parent([&](){ buffer_parent->teardown(); })
5156 );
5157 }
5158 sort_merge_join_parent_pipeline(); // call parent function
5159 }
5160 if (needs_buffer_child) {
5161 FUNCTION(sort_merge_join_child_pipeline, void(void)) // create function for child pipeline
5162 {
5163 auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
5164 M.children[1]->execute(
5165 /* setup= */ setup_t::Make_Without_Parent([&](){ buffer_child->setup(); }),
5166 /* pipeline= */ [&](){ buffer_child->consume(); },
5167 /* teardown= */ teardown_t::Make_Without_Parent([&](){ buffer_child->teardown(); })
5168 );
5169 }
5170 sort_merge_join_child_pipeline(); // call child function
5171 }
5172
5173 /*----- Decompose each clause of the join predicate of the form `A.x = B.y` into parts `A.x` and `B.y`. -----*/
5174 std::vector<SortingOperator::order_type> order_parent, order_child;
5175 for (auto &clause : M.join.predicate()) {
5176 M_insist(clause.size() == 1, "invalid equi-predicate");
5177 auto &literal = clause[0];
5178 auto &binary = as<const BinaryExpr>(literal.expr());
5179 M_insist((not literal.negative() and binary.tok == TK_EQUAL) or
5180 (literal.negative() and binary.tok == TK_BANG_EQUAL), "invalid equi-predicate");
5181 M_insist(is<const Designator>(binary.lhs), "invalid equi-predicate");
5182 M_insist(is<const Designator>(binary.rhs), "invalid equi-predicate");
5183 auto [expr_parent, expr_child] = M.parent.schema().has(Schema::Identifier(*binary.lhs)) ?
5184 std::make_pair(binary.lhs.get(), binary.rhs.get()) : std::make_pair(binary.rhs.get(), binary.lhs.get());
5185 order_parent.emplace_back(*expr_parent, true); // ascending order
5186 order_child.emplace_back(*expr_child, true); // ascending order
5187 }
5188 M_insist(order_parent.size() == order_child.size(), "number of found IDs differ");
5189 M_insist(not order_parent.empty(), "must find at least one ID");
5190
5191 /*----- If necessary, invoke sorting algorithm with buffer to sort. -----*/
5192 if constexpr (SortLeft)
5193 quicksort<CmpPredicated>(*buffer_parent, order_parent);
5194 if constexpr (SortRight)
5195 quicksort<CmpPredicated>(*buffer_child, order_child);
5196
5197 /*----- Create predicate to check if child co-group is smaller or equal than the one of the parent relation. -----*/
5198 auto child_smaller_equal = [&]() -> Boolx1 {
5199 std::optional<Boolx1> child_smaller_equal_;
5200 for (std::size_t i = 0; i < order_child.size(); ++i) {
5201 auto &des_parent = as<const Designator>(order_parent[i].first);
5202 auto &des_child = as<const Designator>(order_child[i].first);
5203 Token leq = Token::CreateArtificial(TK_LESS_EQUAL);
5204 auto cpy_parent = std::make_unique<Designator>(des_parent.tok, des_parent.table_name, des_parent.attr_name,
5205 des_parent.type(), des_parent.target());
5206 auto cpy_child = std::make_unique<Designator>(des_child.tok, des_child.table_name, des_child.attr_name,
5207 des_child.type(), des_child.target());
5208 BinaryExpr expr(std::move(leq), std::move(cpy_child), std::move(cpy_parent));
5209
5210 auto child = env.get(Schema::Identifier(des_child));
5211 Boolx1 cmp = env.compile<_Boolx1>(expr).is_true_and_not_null();
5212 if (child_smaller_equal_)
5213 child_smaller_equal_.emplace(*child_smaller_equal_ and (is_null(child) or cmp));
5214 else
5215 child_smaller_equal_.emplace(is_null(child) or cmp);
5216 }
5217 M_insist(bool(child_smaller_equal_));
5218 return *child_smaller_equal_;
5219 };
5220
5221 /*----- Compile data layouts to generate sequential loads from buffers. -----*/
5222 static Schema empty_schema;
5223 Var<U32x1> tuple_id_parent, tuple_id_child; // default initialized to 0
5224 auto [inits_parent, loads_parent, _jumps_parent] = [&](){
5225 if (needs_buffer_parent) {
5226 return compile_load_sequential(buffer_parent->schema(), empty_schema, buffer_parent->base_address(),
5227 buffer_parent->layout(), 1, buffer_parent->schema(), tuple_id_parent);
5228 } else {
5229 auto &scan = as<const ScanOperator>(M.parent);
5230 return compile_load_sequential(schema_parent, empty_schema, get_base_address(scan.store().table().name()),
5231 scan.store().table().layout(), 1, scan.store().table().schema(scan.alias()),
5232 tuple_id_parent);
5233 }
5234 }();
5235 auto [inits_child, loads_child, _jumps_child] = [&](){
5236 if (needs_buffer_child) {
5237 return compile_load_sequential(buffer_child->schema(), empty_schema, buffer_child->base_address(),
5238 buffer_child->layout(), 1, buffer_child->schema(), tuple_id_child);
5239 } else {
5240 auto &scan = as<const ScanOperator>(M.child);
5241 return compile_load_sequential(schema_child, empty_schema, get_base_address(scan.store().table().name()),
5242 scan.store().table().layout(), 1, scan.store().table().schema(scan.alias()),
5243 tuple_id_child);
5244 }
5245 }();
5246 /* since structured bindings cannot be used in lambda capture */
5247 Block jumps_parent(std::move(_jumps_parent)), jumps_child(std::move(_jumps_child));
5248
5249 /*----- Process both buffers together. -----*/
5250 setup();
5251 inits_parent.attach_to_current();
5252 inits_child.attach_to_current();
5253 U32x1 size_parent = needs_buffer_parent ? buffer_parent->size()
5254 : get_num_rows(as<const ScanOperator>(M.parent).store().table().name());
5255 U32x1 size_child = needs_buffer_child ? buffer_child->size()
5256 : get_num_rows(as<const ScanOperator>(M.child).store().table().name());
5257 WHILE (tuple_id_parent < size_parent and tuple_id_child < size_child) { // neither end reached
5258 loads_parent.attach_to_current();
5259 loads_child.attach_to_current();
5260 if constexpr (Predicated) {
5261 env.add_predicate(M.join.predicate());
5262 pipeline();
5263 } else {
5264 M_insist(CodeGenContext::Get().num_simd_lanes() == 1, "invalid number of SIMD lanes");
5265 IF (env.compile<_Boolx1>(M.join.predicate()).is_true_and_not_null()) { // predicate fulfilled
5266 pipeline();
5267 };
5268 }
5269 IF (child_smaller_equal()) {
5270 jumps_child.attach_to_current();
5271 } ELSE {
5272 jumps_parent.attach_to_current();
5273 };
5274 }
5275 teardown();
5276}
5277
5278
5279/*======================================================================================================================
5280 * Limit
5281 *====================================================================================================================*/
5282
5283ConditionSet Limit::pre_condition(std::size_t child_idx, const std::tuple<const LimitOperator*>&)
5284{
5285 M_insist(child_idx == 0);
5286
5287 ConditionSet pre_cond;
5288
5289 /*----- Limit does not support SIMD. -----*/
5290 pre_cond.add_condition(NoSIMD());
5291
5292 return pre_cond;
5293}
5294
5295void Limit::execute(const Match<Limit> &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
5296{
5297 std::optional<Block> teardown_block;
5298 std::optional<BlockUser> use_teardown;
5299
5300 std::optional<Var<U32x1>> counter;
5301 /* default initialized to 0 */
5302 Global<U32x1> counter_backup;
5303
5304 M.child->execute(
5305 /* setup= */ setup_t(std::move(setup), [&](){
5306 counter.emplace(counter_backup);
5307 teardown_block.emplace("limit.teardown", true); // create block
5308 use_teardown.emplace(*teardown_block); // set block active s.t. it contains all following pipeline code
5309 }),
5310 /* pipeline= */ [&, pipeline=std::move(pipeline)](){
5311 M_insist(bool(teardown_block));
5312 M_insist(bool(counter));
5313 const uint32_t limit = M.limit.offset() + M.limit.limit();
5314
5315 /*----- Abort pipeline, i.e. go to teardown code, if limit is exceeded. -----*/
5316 IF (*counter >= limit) {
5317 GOTO(*teardown_block);
5318 };
5319
5320 /*----- Emit result if in bounds. -----*/
5321 if (M.limit.offset()) {
5322 IF (*counter >= uint32_t(M.limit.offset())) {
5323 Wasm_insist(*counter < limit, "counter must not exceed limit");
5324 pipeline();
5325 };
5326 } else {
5327 Wasm_insist(*counter < limit, "counter must not exceed limit");
5328 pipeline();
5329 }
5330
5331 /*----- Update counter. -----*/
5332 *counter += 1U;
5333 },
5334 /* teardown= */ teardown_t::Make_Without_Parent([&, teardown=std::move(teardown)](){
5335 M_insist(bool(teardown_block));
5336 M_insist(bool(use_teardown));
5337 use_teardown.reset(); // deactivate block
5338 teardown_block.reset(); // emit block containing pipeline code into parent -> GOTO jumps here
5339 teardown(); // *before* own teardown code to *not* jump over it in case of another limit operator
5340 M_insist(bool(counter));
5341 counter_backup = *counter;
5342 counter.reset();
5343 })
5344 );
5345}
5346
5347
5348/*======================================================================================================================
5349 * Grouping combined with Join
5350 *====================================================================================================================*/
5351
5353 std::size_t child_idx,
5354 const std::tuple<const GroupingOperator*, const JoinOperator*, const Wildcard*, const Wildcard*>
5355 &partial_inner_nodes)
5356{
5357 ConditionSet pre_cond;
5358
5359 /*----- Hash-based group-join can only be used if aggregates only depend on either build or probe relation. -----*/
5360 auto &grouping = *std::get<0>(partial_inner_nodes);
5361 for (auto &fn_expr : grouping.aggregates()) {
5362 M_insist(fn_expr.get().args.size() <= 1);
5363 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
5365 }
5366
5367 /*----- Hash-based group-join can only be used for binary joins on equi-predicates. -----*/
5368 auto &join = *std::get<1>(partial_inner_nodes);
5369 if (not join.predicate().is_equi())
5371
5372 M_insist(child_idx < 2);
5373 if (child_idx == 0) {
5374 /*----- Decompose each clause of the join predicate of the form `A.x = B.y` into parts `A.x` and `B.y`. -----*/
5375 auto &build = *std::get<2>(partial_inner_nodes);
5376 const auto build_keys = decompose_equi_predicate(join.predicate(), build.schema()).first;
5377
5378 /*----- Hash-based group-join can only be used if grouping and join (i.e. build) key match (ignoring order). -*/
5379 const auto num_grouping_keys = grouping.group_by().size();
5380 if (num_grouping_keys != build_keys.size()) // XXX: duplicated IDs are still a match but rejected here
5382 for (std::size_t i = 0; i < num_grouping_keys; ++i) {
5383 Schema::Identifier grouping_key(grouping.group_by()[i].first.get());
5384 if (not contains(build_keys, grouping_key))
5386 }
5387 }
5388
5389 /*----- Hash-based group-join does not support SIMD. -----*/
5390 pre_cond.add_condition(NoSIMD());
5391
5392 return pre_cond;
5393}
5394
5396{
5397 return 1.5 * M.build.info().estimated_cardinality + 1.0 * M.probe.info().estimated_cardinality +
5398 1.0 * M.join.info().estimated_cardinality;
5399}
5400
5402{
5403 ConditionSet post_cond;
5404
5405 /*----- Hash-based group-join does not introduce predication (it is already handled by the hash table). -----*/
5406 post_cond.add_condition(Predicated(false));
5407
5408 /*----- Hash-based group-join does not introduce SIMD. -----*/
5409 post_cond.add_condition(NoSIMD());
5410
5411 return post_cond;
5412}
5413
5415 teardown_t teardown)
5416{
5417 // TODO: determine setup
5418 const uint64_t AGGREGATES_SIZE_THRESHOLD_IN_BITS =
5419 M.use_in_place_values ? std::numeric_limits<uint64_t>::max() : 0;
5420
5421 auto &C = Catalog::Get();
5422 const auto num_keys = M.grouping.group_by().size();
5423
5424 /*----- Compute hash table schema and information about aggregates, especially AVG aggregates. -----*/
5425 Schema ht_schema;
5426 for (std::size_t i = 0; i < num_keys; ++i) {
5427 auto &e = M.grouping.schema()[i];
5428 ht_schema.add(e.id, e.type, e.constraints);
5429 }
5430 auto aggregates_info = compute_aggregate_info(M.grouping.aggregates(), M.grouping.schema(), num_keys);
5431 const auto &aggregates = aggregates_info.first;
5432 const auto &avg_aggregates = aggregates_info.second;
5433 bool needs_build_counter = false;
5434 uint64_t aggregates_size_in_bits = 0;
5435 for (auto &info : aggregates) {
5436 ht_schema.add(info.entry);
5437 aggregates_size_in_bits += info.entry.type->size();
5438
5439 /* Add additional COUNT per group during build phase if COUNT or SUM dependent on probe relation occurs. */
5440 if (info.fnid == m::Function::FN_COUNT or info.fnid == m::Function::FN_SUM) {
5441 if (not info.args.empty()) {
5442 M_insist(info.args.size() == 1, "aggregate functions expect at most one argument");
5443 auto &des = as<const Designator>(*info.args[0]);
5444 Schema::Identifier arg(des.table_name.text, des.attr_name.text.assert_not_none());
5445 if (M.probe.schema().has(arg))
5446 needs_build_counter = true;
5447 }
5448 }
5449 }
5450 if (needs_build_counter) {
5451 ht_schema.add(Schema::Identifier(C.pool("$build_counter")), Type::Get_Integer(Type::TY_Scalar, 8),
5453 aggregates_size_in_bits += 64;
5454 }
5455 ht_schema.add(Schema::Identifier(C.pool("$probe_counter")), Type::Get_Integer(Type::TY_Scalar, 8),
5457 aggregates_size_in_bits += 64;
5458
5459 /*----- Decompose each clause of the join predicate of the form `A.x = B.y` into parts `A.x` and `B.y`. -----*/
5460 const auto [build_keys, probe_keys] = decompose_equi_predicate(M.join.predicate(), M.build.schema());
5461 M_insist(build_keys.size() == num_keys);
5462
5463 /*----- Compute initial capacity of hash table. -----*/
5464 uint32_t initial_capacity = compute_initial_ht_capacity(M.grouping, M.load_factor);
5465
5466 /*----- Create hash table for build relation. -----*/
5467 std::unique_ptr<HashTable> ht;
5468 std::vector<HashTable::index_t> key_indices(num_keys);
5469 std::iota(key_indices.begin(), key_indices.end(), 0);
5470 if (M.use_open_addressing_hashing) {
5471 if (aggregates_size_in_bits < AGGREGATES_SIZE_THRESHOLD_IN_BITS)
5472 ht = std::make_unique<GlobalOpenAddressingInPlaceHashTable>(ht_schema, std::move(key_indices),
5473 initial_capacity);
5474 else
5475 ht = std::make_unique<GlobalOpenAddressingOutOfPlaceHashTable>(ht_schema, std::move(key_indices),
5476 initial_capacity);
5477 if (M.use_quadratic_probing)
5478 as<OpenAddressingHashTableBase>(*ht).set_probing_strategy<QuadraticProbing>();
5479 else
5480 as<OpenAddressingHashTableBase>(*ht).set_probing_strategy<LinearProbing>();
5481 } else {
5482 ht = std::make_unique<GlobalChainedHashTable>(ht_schema, std::move(key_indices), initial_capacity);
5483 }
5484
5485 std::optional<HashTable::entry_t> dummy;
5486
5493 auto compile_aggregates = [&](HashTable::entry_t &entry, const Environment &env, const Schema &schema,
5494 bool build_phase) -> std::tuple<Block, Block, Block>
5495 {
5496 Block init_aggs("hash_based_group_join.init_aggs", false),
5497 update_aggs("hash_based_group_join.update_aggs", false),
5498 update_avg_aggs("hash_based_group_join.update_avg_aggs", false);
5499 for (auto &info : aggregates) {
5500 bool is_min = false;
5501 switch (info.fnid) {
5502 default:
5503 M_unreachable("unsupported aggregate function");
5504 case m::Function::FN_MIN:
5505 is_min = true; // set flag and delegate to MAX case
5506 case m::Function::FN_MAX: {
5507 M_insist(info.args.size() == 1, "MIN and MAX aggregate functions expect exactly one argument");
5508 auto &arg = as<const Designator>(*info.args[0]);
5509 const bool bound = schema.has(Schema::Identifier(arg.table_name.text,
5510 arg.attr_name.text.assert_not_none()));
5511
5512 std::visit(overloaded {
5513 [&]<sql_type _T>(HashTable::reference_t<_T> &&r) -> void
5514 requires (not (std::same_as<_T, _Boolx1> or std::same_as<_T, NChar>)) {
5515 using type = typename _T::type;
5516 using T = PrimitiveExpr<type>;
5517
5518 if (build_phase) {
5519 BLOCK_OPEN(init_aggs) {
5520 auto neutral = is_min ? T(std::numeric_limits<type>::max())
5521 : T(std::numeric_limits<type>::lowest());
5522 if (bound) {
5523 auto _arg = env.compile(arg);
5524 auto [val_, is_null] = convert<_T>(_arg).split();
5525 T val(val_); // due to structured binding and lambda closure
5526 IF (is_null) {
5527 r.clone().set_value(neutral); // initialize with neutral element +inf or -inf
5528 if (info.entry.nullable())
5529 r.clone().set_null_bit(Boolx1(true)); // first value is NULL
5530 } ELSE {
5531 r.clone().set_value(val); // initialize with first value
5532 if (info.entry.nullable())
5533 r.clone().set_null_bit(Boolx1(false)); // first value is not NULL
5534 };
5535 } else {
5536 r.clone().set_value(neutral); // initialize with neutral element +inf or -inf
5537 if (info.entry.nullable())
5538 r.clone().set_null_bit(Boolx1(true)); // initialize with neutral element NULL
5539 }
5540 }
5541 }
5542 if (not bound) {
5543 r.discard();
5544 return; // MIN and MAX does not change in phase when argument is unbound
5545 }
5546 BLOCK_OPEN(update_aggs) {
5547 auto _arg = env.compile(arg);
5548 _T _new_val = convert<_T>(_arg);
5549 if (_new_val.can_be_null()) {
5550 auto [new_val_, new_val_is_null_] = _new_val.split();
5551 auto [old_min_max_, old_min_max_is_null] = _T(r.clone()).split();
5552 const Var<Boolx1> new_val_is_null(new_val_is_null_); // due to multiple uses
5553
5554 auto chosen_r = Select(new_val_is_null, dummy->extract<_T>(info.entry.id), r.clone());
5555 if constexpr (std::floating_point<type>) {
5556 chosen_r.set_value(
5557 is_min ? min(old_min_max_, new_val_) // update old min with new value
5558 : max(old_min_max_, new_val_) // update old max with new value
5559 ); // if new value is NULL, only dummy is written
5560 } else {
5561 const Var<T> new_val(new_val_),
5562 old_min_max(old_min_max_); // due to multiple uses
5563 auto cmp = is_min ? new_val < old_min_max : new_val > old_min_max;
5564 chosen_r.set_value(
5565 Select(cmp,
5566 new_val, // update to new value
5567 old_min_max) // do not update
5568 ); // if new value is NULL, only dummy is written
5569 }
5570 r.set_null_bit(
5571 old_min_max_is_null and new_val_is_null // MIN/MAX is NULL iff all values are NULL
5572 );
5573 } else {
5574 auto new_val_ = _new_val.insist_not_null();
5575 auto old_min_max_ = _T(r.clone()).insist_not_null();
5576 if constexpr (std::floating_point<type>) {
5577 r.set_value(
5578 is_min ? min(old_min_max_, new_val_) // update old min with new value
5579 : max(old_min_max_, new_val_) // update old max with new value
5580 );
5581 } else {
5582 const Var<T> new_val(new_val_),
5583 old_min_max(old_min_max_); // due to multiple uses
5584 auto cmp = is_min ? new_val < old_min_max : new_val > old_min_max;
5585 r.set_value(
5586 Select(cmp,
5587 new_val, // update to new value
5588 old_min_max) // do not update
5589 );
5590 }
5591 /* do not update NULL bit since it is already set to `false` */
5592 }
5593 }
5594 },
5595 []<sql_type _T>(HashTable::reference_t<_T>&&) -> void
5596 requires std::same_as<_T,_Boolx1> or std::same_as<_T, NChar> {
5597 M_unreachable("invalid type");
5598 },
5599 [](std::monostate) -> void { M_unreachable("invalid reference"); },
5600 }, entry.extract(info.entry.id));
5601 break;
5602 }
5603 case m::Function::FN_AVG: {
5604 auto it = avg_aggregates.find(info.entry.id);
5605 M_insist(it != avg_aggregates.end());
5606 const auto &avg_info = it->second;
5607 M_insist(avg_info.compute_running_avg,
5608 "AVG aggregate may only occur for running average computations");
5609 M_insist(info.args.size() == 1, "AVG aggregate function expects exactly one argument");
5610 auto &arg = as<const Designator>(*info.args[0]);
5611 const bool bound = schema.has(Schema::Identifier(arg.table_name.text,
5612 arg.attr_name.text.assert_not_none()));
5613
5614 auto r = entry.extract<_Doublex1>(info.entry.id);
5615
5616 if (build_phase) {
5617 BLOCK_OPEN(init_aggs) {
5618 if (bound) {
5619 auto _arg = env.compile(arg);
5620 auto [val_, is_null] = convert<_Doublex1>(_arg).split();
5621 Doublex1 val(val_); // due to structured binding and lambda closure
5622 IF (is_null) {
5623 r.clone().set_value(Doublex1(0.0)); // initialize with neutral element 0
5624 if (info.entry.nullable())
5625 r.clone().set_null_bit(Boolx1(true)); // first value is NULL
5626 } ELSE {
5627 r.clone().set_value(val); // initialize with first value
5628 if (info.entry.nullable())
5629 r.clone().set_null_bit(Boolx1(false)); // first value is not NULL
5630 };
5631 } else {
5632 r.clone().set_value(Doublex1(0.0)); // initialize with neutral element 0
5633 if (info.entry.nullable())
5634 r.clone().set_null_bit(Boolx1(true)); // initialize with neutral element NULL
5635 }
5636 }
5637 }
5638 if (not bound) {
5639 r.discard();
5640 break; // AVG does not change in phase when argument is unbound
5641 }
5642 BLOCK_OPEN(update_avg_aggs) {
5643 /* Compute AVG as iterative mean as described in Knuth, The Art of Computer Programming
5644 * Vol 2, section 4.2.2. */
5645 auto _arg = env.compile(arg);
5646 _Doublex1 _new_val = convert<_Doublex1>(_arg);
5647 if (_new_val.can_be_null()) {
5648 auto [new_val, new_val_is_null_] = _new_val.split();
5649 auto [old_avg_, old_avg_is_null] = _Doublex1(r.clone()).split();
5650 const Var<Boolx1> new_val_is_null(new_val_is_null_); // due to multiple uses
5651 const Var<Doublex1> old_avg(old_avg_); // due to multiple uses
5652
5653 auto delta_absolute = new_val - old_avg;
5654 auto running_count = _I64x1(entry.get<_I64x1>(avg_info.running_count)).insist_not_null();
5655 auto delta_relative = delta_absolute / running_count.to<double>();
5656
5657 auto chosen_r = Select(new_val_is_null, dummy->extract<_Doublex1>(info.entry.id), r.clone());
5658 chosen_r.set_value(
5659 old_avg + delta_relative // update old average with new value
5660 ); // if new value is NULL, only dummy is written
5661 r.set_null_bit(
5662 old_avg_is_null and new_val_is_null // AVG is NULL iff all values are NULL
5663 );
5664 } else {
5665 auto new_val = _new_val.insist_not_null();
5666 auto old_avg_ = _Doublex1(r.clone()).insist_not_null();
5667 const Var<Doublex1> old_avg(old_avg_); // due to multiple uses
5668
5669 auto delta_absolute = new_val - old_avg;
5670 auto running_count = _I64x1(entry.get<_I64x1>(avg_info.running_count)).insist_not_null();
5671 auto delta_relative = delta_absolute / running_count.to<double>();
5672 r.set_value(
5673 old_avg + delta_relative // update old average with new value
5674 );
5675 /* do not update NULL bit since it is already set to `false` */
5676 }
5677 }
5678 break;
5679 }
5680 case m::Function::FN_SUM: {
5681 M_insist(info.args.size() == 1, "SUM aggregate function expects exactly one argument");
5682 auto &arg = as<const Designator>(*info.args[0]);
5683 const bool bound = schema.has(Schema::Identifier(arg.table_name.text,
5684 arg.attr_name.text.assert_not_none()));
5685
5686 std::visit(overloaded {
5687 [&]<sql_type _T>(HashTable::reference_t<_T> &&r) -> void
5688 requires (not (std::same_as<_T, _Boolx1> or std::same_as<_T, NChar>)) {
5689 using type = typename _T::type;
5690 using T = PrimitiveExpr<type>;
5691
5692 if (build_phase) {
5693 BLOCK_OPEN(init_aggs) {
5694 if (bound) {
5695 auto _arg = env.compile(arg);
5696 auto [val_, is_null] = convert<_T>(_arg).split();
5697 T val(val_); // due to structured binding and lambda closure
5698 IF (is_null) {
5699 r.clone().set_value(T(type(0))); // initialize with neutral element 0
5700 if (info.entry.nullable())
5701 r.clone().set_null_bit(Boolx1(true)); // first value is NULL
5702 } ELSE {
5703 r.clone().set_value(val); // initialize with first value
5704 if (info.entry.nullable())
5705 r.clone().set_null_bit(Boolx1(false)); // first value is not NULL
5706 };
5707 } else {
5708 r.clone().set_value(T(type(0))); // initialize with neutral element 0
5709 if (info.entry.nullable())
5710 r.clone().set_null_bit(Boolx1(true)); // initialize with neutral element NULL
5711 }
5712 }
5713 }
5714 if (not bound) {
5715 r.discard();
5716 return; // SUM may later be multiplied with group counter but does not change here
5717 }
5718 BLOCK_OPEN(update_aggs) {
5719 auto _arg = env.compile(arg);
5720 _T _new_val = convert<_T>(_arg);
5721 if (_new_val.can_be_null()) {
5722 auto [new_val, new_val_is_null_] = _new_val.split();
5723 auto [old_sum, old_sum_is_null] = _T(r.clone()).split();
5724 const Var<Boolx1> new_val_is_null(new_val_is_null_); // due to multiple uses
5725
5726 auto chosen_r = Select(new_val_is_null, dummy->extract<_T>(info.entry.id), r.clone());
5727 chosen_r.set_value(
5728 old_sum + new_val // add new value to old sum
5729 ); // if new value is NULL, only dummy is written
5730 r.set_null_bit(
5731 old_sum_is_null and new_val_is_null // SUM is NULL iff all values are NULL
5732 );
5733 } else {
5734 auto new_val = _new_val.insist_not_null();
5735 auto old_sum = _T(r.clone()).insist_not_null();
5736 r.set_value(
5737 old_sum + new_val // add new value to old sum
5738 );
5739 /* do not update NULL bit since it is already set to `false` */
5740 }
5741 }
5742 },
5743 []<sql_type _T>(HashTable::reference_t<_T>&&) -> void
5744 requires std::same_as<_T,_Boolx1> or std::same_as<_T, NChar> {
5745 M_unreachable("invalid type");
5746 },
5747 [](std::monostate) -> void { M_unreachable("invalid reference"); },
5748 }, entry.extract(info.entry.id));
5749 break;
5750 }
5751 case m::Function::FN_COUNT: {
5752 M_insist(info.args.size() <= 1, "COUNT aggregate function expects at most one argument");
5753
5754 auto r = entry.get<_I64x1>(info.entry.id); // do not extract to be able to access for AVG case
5755
5756 if (info.args.empty()) {
5757 if (not build_phase) {
5758 r.discard();
5759 break; // COUNT(*) will later be multiplied with probe counter but only changes in build phase
5760 }
5761 BLOCK_OPEN(init_aggs) {
5762 r.clone() = _I64x1(1); // initialize with 1 (for first value)
5763 }
5764 BLOCK_OPEN(update_aggs) {
5765 auto old_count = _I64x1(r.clone()).insist_not_null();
5766 r.set_value(
5767 old_count + int64_t(1) // increment old count by 1
5768 );
5769 /* do not update NULL bit since it is already set to `false` */
5770 }
5771 } else {
5772 auto &arg = as<const Designator>(*info.args[0]);
5773 const bool bound = schema.has(Schema::Identifier(arg.table_name.text,
5774 arg.attr_name.text.assert_not_none()));
5775
5776 if (build_phase) {
5777 BLOCK_OPEN(init_aggs) {
5778 if (bound) {
5779 auto _arg = env.compile(arg);
5780 I64x1 new_val_not_null =
5781 can_be_null(_arg) ? not_null(_arg).to<int64_t>()
5782 : (discard(_arg), I64x1(1)); // discard since no use
5783 r.clone() = _I64x1(new_val_not_null); // initialize with 1 iff first value is present
5784 } else {
5785 r.clone() = _I64x1(0); // initialize with neutral element 0
5786 }
5787 }
5788 }
5789 if (not bound) {
5790 r.discard();
5791 break; // COUNT may later be multiplied with group counter but does not change here
5792 }
5793 BLOCK_OPEN(update_aggs) {
5794 auto _arg = env.compile(arg);
5795 I64x1 new_val_not_null =
5796 can_be_null(_arg) ? not_null(_arg).to<int64_t>()
5797 : (discard(_arg), I64x1(1)); // discard since no use
5798 auto old_count = _I64x1(r.clone()).insist_not_null();
5799 r.set_value(
5800 old_count + new_val_not_null // increment old count by 1 iff new value is present
5801 );
5802 /* do not update NULL bit since it is already set to `false` */
5803 }
5804 }
5805 break;
5806 }
5807 }
5808 }
5809 return { std::move(init_aggs), std::move(update_aggs), std::move(update_avg_aggs) };
5810 };
5811
5812 /*----- Create function for build child. -----*/
5813 FUNCTION(hash_based_group_join_build_child_pipeline, void(void)) // create function for pipeline
5814 {
5815 auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
5816
5817 M.children[0]->execute(
5818 /* setup= */ setup_t::Make_Without_Parent([&](){
5819 ht->setup();
5820 ht->set_high_watermark(M.load_factor);
5821 dummy.emplace(ht->dummy_entry()); // create dummy slot to ignore NULL values in aggregate computations
5822 }),
5823 /* pipeline= */ [&](){
5824 M_insist(bool(dummy));
5825 const auto &env = CodeGenContext::Get().env();
5826
5827 std::optional<Boolx1> build_key_not_null;
5828 for (auto &build_key : build_keys) {
5829 auto val = env.get(build_key);
5830 if (build_key_not_null)
5831 build_key_not_null.emplace(*build_key_not_null and not_null(val));
5832 else
5833 build_key_not_null.emplace(not_null(val));
5834 }
5835 M_insist(bool(build_key_not_null));
5836 IF (*build_key_not_null) { // TODO: predicated version
5837 /*----- Insert key if not yet done. -----*/
5838 std::vector<SQL_t> key;
5839 for (auto &build_key : build_keys)
5840 key.emplace_back(env.get(build_key));
5841 auto [entry, inserted] = ht->try_emplace(std::move(key));
5842
5843 /*----- Compile aggregates. -----*/
5844 auto t = compile_aggregates(entry, env, M.build.schema(), /* build_phase= */ true);
5845 auto &init_aggs = std::get<0>(t);
5846 auto &update_aggs = std::get<1>(t);
5847 auto &update_avg_aggs = std::get<2>(t);
5848
5849 /*----- Add group counters to compiled aggregates. -----*/
5850 if (needs_build_counter) {
5851 auto r = entry.extract<_I64x1>(C.pool("$build_counter"));
5852 BLOCK_OPEN(init_aggs) {
5853 r.clone() = _I64x1(1); // initialize with 1 (for first value)
5854 }
5855 BLOCK_OPEN(update_aggs) {
5856 auto old_count = _I64x1(r.clone()).insist_not_null();
5857 r.set_value(
5858 old_count + int64_t(1) // increment old count by 1
5859 );
5860 /* do not update NULL bit since it is already set to `false` */
5861 }
5862 }
5863 BLOCK_OPEN(init_aggs) {
5864 auto r = entry.extract<_I64x1>(C.pool("$probe_counter"));
5865 r = _I64x1(0); // initialize with neutral element 0
5866 }
5867
5868 /*----- If group has been inserted, initialize aggregates. Otherwise, update them. -----*/
5869 IF (inserted) {
5870 init_aggs.attach_to_current();
5871 } ELSE {
5872 update_aggs.attach_to_current();
5873 update_avg_aggs.attach_to_current(); // after others to ensure that running count is incremented before
5874 };
5875 };
5876 },
5877 /* teardown= */ teardown_t::Make_Without_Parent([&](){ ht->teardown(); })
5878 );
5879 }
5880 hash_based_group_join_build_child_pipeline(); // call build child function
5881
5882 /*----- Create function for probe child. -----*/
5883 FUNCTION(hash_based_group_join_probe_child_pipeline, void(void)) // create function for pipeline
5884 {
5885 auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment for this function
5886
5887 M.children[1]->execute(
5888 /* setup= */ setup_t::Make_Without_Parent([&](){
5889 ht->setup();
5890 dummy.emplace(ht->dummy_entry()); // create dummy slot to ignore NULL values in aggregate computations
5891 }),
5892 /* pipeline= */ [&](){
5893 M_insist(bool(dummy));
5894 const auto &env = CodeGenContext::Get().env();
5895
5896 /* TODO: may check for NULL on probe keys as well, branching + predicated version */
5897 /*----- Probe with probe key. -----*/
5898 std::vector<SQL_t> key;
5899 for (auto &probe_key : probe_keys)
5900 key.emplace_back(env.get(probe_key));
5901 auto [entry, found] = ht->find(std::move(key));
5902
5903 /*----- Compile aggregates. -----*/
5904 auto t = compile_aggregates(entry, env, M.probe.schema(), /* build_phase= */ false);
5905 auto &init_aggs = std::get<0>(t);
5906 auto &update_aggs = std::get<1>(t);
5907 auto &update_avg_aggs = std::get<2>(t);
5908
5909 /*----- Add probe counter to compiled aggregates. -----*/
5910 BLOCK_OPEN(update_aggs) {
5911 auto r = entry.extract<_I64x1>(C.pool("$probe_counter"));
5912 auto old_count = _I64x1(r.clone()).insist_not_null();
5913 r.set_value(
5914 old_count + int64_t(1) // increment old count by 1
5915 );
5916 /* do not update NULL bit since it is already set to `false` */
5917 }
5918
5919 /*----- If group has been inserted, initialize aggregates. Otherwise, update them. -----*/
5920 M_insist(init_aggs.empty(), "aggregates must be initialized in build phase");
5921 IF (found) {
5922 update_aggs.attach_to_current();
5923 update_avg_aggs.attach_to_current(); // after others to ensure that running count is incremented before
5924 };
5925 },
5926 /* teardown= */ teardown_t::Make_Without_Parent([&](){ ht->teardown(); })
5927 );
5928 }
5929 hash_based_group_join_probe_child_pipeline(); // call probe child function
5930
5931 auto &env = CodeGenContext::Get().env();
5932
5933 /*----- Process each computed group. -----*/
5934 setup_t(std::move(setup), [&](){ ht->setup(); })();
5935 ht->for_each([&, pipeline=std::move(pipeline)](HashTable::const_entry_t entry){
5936 /*----- Check whether probe match was found. -----*/
5937 I64x1 probe_counter = _I64x1(entry.get<_I64x1>(C.pool("$probe_counter"))).insist_not_null();
5938 IF (probe_counter != int64_t(0)) {
5939 /*----- Compute key schema to detect duplicated keys. -----*/
5940 Schema key_schema;
5941 for (std::size_t i = 0; i < num_keys; ++i) {
5942 auto &e = M.grouping.schema()[i];
5943 key_schema.add(e.id, e.type, e.constraints);
5944 }
5945
5946 /*----- Add computed group tuples to current environment. ----*/
5947 for (auto &e : M.grouping.schema().deduplicate()) {
5948 try {
5949 key_schema.find(e.id);
5950 } catch (invalid_argument&) {
5951 continue; // skip duplicated keys since they must not be used afterwards
5952 }
5953
5954 if (auto it = avg_aggregates.find(e.id);
5955 it != avg_aggregates.end() and not it->second.compute_running_avg)
5956 { // AVG aggregates which is not yet computed, divide computed sum with computed count
5957 auto &avg_info = it->second;
5958 auto sum = std::visit(overloaded {
5959 [&]<sql_type T>(HashTable::const_reference_t<T> &&r) -> _Doublex1
5960 requires (std::same_as<T, _I64x1> or std::same_as<T, _Doublex1>) {
5961 return T(r).template to<double>();
5962 },
5963 [](auto&&) -> _Doublex1 { M_unreachable("invalid type"); },
5964 [](std::monostate&&) -> _Doublex1 { M_unreachable("invalid reference"); },
5965 }, entry.get(avg_info.sum));
5966 auto count = _I64x1(entry.get<_I64x1>(avg_info.running_count)).insist_not_null().to<double>();
5967 auto avg = sum / count; // no need to multiply with group counter as the factor would not change the fraction
5968 if (avg.can_be_null()) {
5969 _Var<Doublex1> var(avg); // introduce variable s.t. uses only load from it
5970 env.add(e.id, var);
5971 } else {
5972 /* introduce variable w/o NULL bit s.t. uses only load from it */
5973 Var<Doublex1> var(avg.insist_not_null());
5974 env.add(e.id, _Doublex1(var));
5975 }
5976 } else { // part of key or already computed aggregate (without multiplication with group counter)
5977 std::visit(overloaded {
5978 [&]<typename T>(HashTable::const_reference_t<Expr<T>> &&r) -> void {
5979 Expr<T> value = r;
5980
5981 auto pred = [&e](const auto &info) -> bool { return info.entry.id == e.id; };
5982 if (auto it = std::find_if(aggregates.cbegin(), aggregates.cend(), pred);
5983 it != aggregates.cend())
5984 { // aggregate
5985 /* For COUNT and SUM, multiply current aggregate value with respective group counter
5986 * since only tuples in phase in which argument is bound are counted/summed up. */
5987 if (it->args.empty()) {
5988 M_insist(it->fnid == m::Function::FN_COUNT,
5989 "only COUNT aggregate function may have no argument");
5990 I64x1 probe_counter =
5991 _I64x1(entry.get<_I64x1>(C.pool("$probe_counter"))).insist_not_null();
5992 PrimitiveExpr<T> count = value.insist_not_null() * probe_counter.to<T>();
5993 Var<PrimitiveExpr<T>> var(count); // introduce variable s.t. uses only load from it
5994 env.add(e.id, Expr<T>(var));
5995 return; // next group tuple entry
5996 } else {
5997 M_insist(it->args.size() == 1, "aggregate functions expect at most one argument");
5998 auto &des = as<const Designator>(*it->args[0]);
5999 Schema::Identifier arg(des.table_name.text, des.attr_name.text.assert_not_none());
6000 if (it->fnid == m::Function::FN_COUNT or it->fnid == m::Function::FN_SUM) {
6001 if (M.probe.schema().has(arg)) {
6002 I64x1 build_counter =
6003 _I64x1(entry.get<_I64x1>(C.pool("$build_counter"))).insist_not_null();
6004 auto agg = value * build_counter.to<T>();
6005 if (agg.can_be_null()) {
6006 Var<Expr<T>> var(agg); // introduce variable s.t. uses only load from it
6007 env.add(e.id, var);
6008 } else {
6009 /* introduce variable w/o NULL bit s.t. uses only load from it */
6010 Var<PrimitiveExpr<T>> var(agg.insist_not_null());
6011 env.add(e.id, Expr<T>(var));
6012 }
6013 } else {
6014 M_insist(M.build.schema().has(arg),
6015 "argument ID must occur in either child schema");
6016 I64x1 probe_counter =
6017 _I64x1(entry.get<_I64x1>(C.pool("$probe_counter"))).insist_not_null();
6018 auto agg = value * probe_counter.to<T>();
6019 if (agg.can_be_null()) {
6020 Var<Expr<T>> var(agg); // introduce variable s.t. uses only load from it
6021 env.add(e.id, var);
6022 } else {
6023 /* introduce variable w/o NULL bit s.t. uses only load from it */
6024 Var<PrimitiveExpr<T>> var(agg.insist_not_null());
6025 env.add(e.id, Expr<T>(var));
6026 }
6027 }
6028 return; // next group tuple entry
6029 }
6030 }
6031 }
6032
6033 /* fallthrough: part of key or correctly computed aggregate */
6034 if (value.can_be_null()) {
6035 Var<Expr<T>> var(value); // introduce variable s.t. uses only load from it
6036 env.add(e.id, var);
6037 } else {
6038 /* introduce variable w/o NULL bit s.t. uses only load from it */
6039 Var<PrimitiveExpr<T>> var(value.insist_not_null());
6040 env.add(e.id, Expr<T>(var));
6041 }
6042 },
6043 [&](HashTable::const_reference_t<_Boolx1> &&r) -> void {
6044#ifndef NDEBUG
6045 auto pred = [&e](const auto &info) -> bool { return info.entry.id == e.id; };
6046 M_insist(std::find_if(aggregates.cbegin(), aggregates.cend(), pred) == aggregates.cend(),
6047 "booleans must not be the result of aggregate functions");
6048#endif
6049 _Boolx1 value = r;
6050 if (value.can_be_null()) {
6051 _Var<Boolx1> var(value); // introduce variable s.t. uses only load from it
6052 env.add(e.id, var);
6053 } else {
6054 /* introduce variable w/o NULL bit s.t. uses only load from it */
6055 Var<Boolx1> var(value.insist_not_null());
6056 env.add(e.id, _Boolx1(var));
6057 }
6058 },
6059 [&](HashTable::const_reference_t<NChar> &&r) -> void {
6060#ifndef NDEBUG
6061 auto pred = [&e](const auto &info) -> bool { return info.entry.id == e.id; };
6062 M_insist(std::find_if(aggregates.cbegin(), aggregates.cend(), pred) == aggregates.cend(),
6063 "strings must not be the result of aggregate functions");
6064#endif
6065 NChar value(r);
6066 Var<Ptr<Charx1>> var(value.val()); // introduce variable s.t. uses only load from it
6067 env.add(e.id, NChar(var, value.can_be_null(), value.length(),
6068 value.guarantees_terminating_nul()));
6069 },
6070 [](std::monostate&&) -> void { M_unreachable("invalid reference"); },
6071 }, entry.get(e.id)); // do not extract to be able to access for not-yet-computed AVG aggregates
6072 }
6073 }
6074
6075 /*----- Resume pipeline. -----*/
6076 pipeline();
6077 };
6078 });
6079 teardown_t(std::move(teardown), [&](){ ht->teardown(); })();
6080}
6081
6082
6083/*======================================================================================================================
6084 * Match<T>::print()
6085 *====================================================================================================================*/
6086
6088{
6089 const Operator &op;
6090
6091 friend std::ostream & operator<<(std::ostream &out, const print_info &info) {
6092 if (info.op.has_info())
6093 out << " <" << info.op.info().estimated_cardinality << '>';
6094 return out;
6095 }
6096};
6097
6098void Match<m::wasm::NoOp>::print(std::ostream &out, unsigned level) const
6099{
6100 indent(out, level) << "wasm::NoOp" << print_info(this->noop) << " (cumulative cost " << cost() << ')';
6101 this->child->print(out, level + 1);
6102}
6103
6104template<bool SIMDfied>
6105void Match<m::wasm::Callback<SIMDfied>>::print(std::ostream &out, unsigned level) const
6106{
6107 indent(out, level) << "wasm::Callback with " << this->result_set_window_size << " tuples result set "
6108 << this->callback.schema() << print_info(this->callback)
6109 << " (cumulative cost " << cost() << ')';
6110 this->child->print(out, level + 1);
6111}
6112
6113template<bool SIMDfied>
6114void Match<m::wasm::Print<SIMDfied>>::print(std::ostream &out, unsigned level) const
6115{
6116 indent(out, level) << "wasm::Print with " << this->result_set_window_size << " tuples result set "
6117 << this->print_op.schema() << print_info(this->print_op)
6118 << " (cumulative cost " << cost() << ')';
6119 this->child->print(out, level + 1);
6120}
6121
6122template<bool SIMDfied>
6123void Match<m::wasm::Scan<SIMDfied>>::print(std::ostream &out, unsigned level) const
6124{
6125 indent(out, level) << (SIMDfied ? "wasm::SIMDScan(" : "wasm::Scan(") << this->scan.alias() << ") ";
6126 if (this->buffer_factory_ and this->scan.schema().drop_constants().deduplicate().num_entries())
6127 out << "with " << this->buffer_num_tuples_ << " tuples output buffer ";
6128 out << this->scan.schema() << print_info(this->scan) << " (cumulative cost " << cost() << ')';
6129}
6130
6131template<idx::IndexMethod IndexMethod>
6132void Match<m::wasm::IndexScan<IndexMethod>>::print(std::ostream &out, unsigned level) const
6133{
6134 if (IndexMethod == idx::IndexMethod::Array)
6135 indent(out, level) << "wasm::ArrayIndexScan(";
6136 else if (IndexMethod == idx::IndexMethod::Rmi)
6137 indent(out, level) << "wasm::RecursiveModelIndexScan(";
6138 else
6139 M_unreachable("unknown index");
6140
6142 out << "Compilation[";
6144 out << "Callback";
6146 out << "ExposedMemory";
6147 else
6148 M_unreachable("unknown compilation strategy");
6150 out << "Interpretation[";
6152 out << "Inline";
6154 out << "Memory";
6155 else
6156 M_unreachable("unknown materialization strategy");
6158 out << "Hybrid[";
6160 out << "Inline,";
6162 out << "Memory,";
6163 else
6164 M_unreachable("unknown materialization strategy");
6166 out << "Callback";
6168 out << "ExposedMemory";
6169 else
6170 M_unreachable("unknown compilation strategy");
6171 } else {
6172 M_unreachable("unknown strategy");
6173 }
6174
6175 out << "], " << this->scan.alias() << ", " << this->filter.filter() << ") ";
6176 if (this->buffer_factory_ and this->scan.schema().drop_constants().deduplicate().num_entries())
6177 out << "with " << this->buffer_num_tuples_ << " tuples output buffer ";
6178 out << this->scan.schema() << print_info(this->scan) << " (cumulative cost " << cost() << ')';
6179}
6180
6181template<bool Predicated>
6182void Match<m::wasm::Filter<Predicated>>::print(std::ostream &out, unsigned level) const
6183{
6184 indent(out, level) << "wasm::" << (Predicated ? "Predicated" : "Branching") << "Filter ";
6185 if (this->buffer_factory_ and this->filter.schema().drop_constants().deduplicate().num_entries())
6186 out << "with " << this->buffer_num_tuples_ << " tuples output buffer ";
6187 out << this->filter.schema() << print_info(this->filter) << " (cumulative cost " << cost() << ')';
6188 this->child->print(out, level + 1);
6189}
6190
6191void Match<m::wasm::LazyDisjunctiveFilter>::print(std::ostream &out, unsigned level) const
6192{
6193 indent(out, level) << "wasm::LazyDisjunctiveFilter ";
6194 if (this->buffer_factory_ and this->filter.schema().drop_constants().deduplicate().num_entries())
6195 out << "with " << this->buffer_num_tuples_ << " tuples output buffer ";
6196 const cnf::Clause &clause = this->filter.filter()[0];
6197 for (auto it = clause.cbegin(); it != clause.cend(); ++it) {
6198 if (it != clause.cbegin()) out << " → ";
6199 out << *it;
6200 }
6201 out << ' ' << this->filter.schema() << print_info(this->filter) << " (cumulative cost " << cost() << ')';
6202 this->child->print(out, level + 1);
6203}
6204
6205void Match<m::wasm::Projection>::print(std::ostream &out, unsigned level) const
6206{
6207 indent(out, level) << "wasm::Projection ";
6208 if (this->buffer_factory_ and this->projection.schema().drop_constants().deduplicate().num_entries())
6209 out << "with " << this->buffer_num_tuples_ << " tuples output buffer ";
6210 out << this->projection.schema() << print_info(this->projection) << " (cumulative cost " << cost() << ')';
6211 if (this->child)
6212 this->child->get()->print(out, level + 1);
6213}
6214
6215void Match<m::wasm::HashBasedGrouping>::print(std::ostream &out, unsigned level) const
6216{
6217 indent(out, level) << "wasm::HashBasedGrouping " << this->grouping.schema() << print_info(this->grouping)
6218 << " (cumulative cost " << cost() << ')';
6219 this->child->print(out, level + 1);
6220}
6221
6222void Match<m::wasm::OrderedGrouping>::print(std::ostream &out, unsigned level) const
6223{
6224 indent(out, level) << "wasm::OrderedGrouping " << this->grouping.schema() << print_info(this->grouping)
6225 << " (cumulative cost " << cost() << ')';
6226 this->child->print(out, level + 1);
6227}
6228
6229void Match<m::wasm::Aggregation>::print(std::ostream &out, unsigned level) const
6230{
6231 indent(out, level) << "wasm::Aggregation " << this->aggregation.schema() << print_info(this->aggregation)
6232 << " (cumulative cost " << cost() << ')';
6233 this->child->print(out, level + 1);
6234}
6235
6236template<bool CmpPredicated>
6237void Match<m::wasm::Quicksort<CmpPredicated>>::print(std::ostream &out, unsigned level) const
6238{
6239 indent(out, level) << "wasm::" << (CmpPredicated ? "Predicated" : "") << "Quicksort " << this->sorting.schema()
6240 << print_info(this->sorting) << " (cumulative cost " << cost() << ')';
6241 this->child->print(out, level + 1);
6242}
6243
6244void Match<m::wasm::NoOpSorting>::print(std::ostream &out, unsigned level) const
6245{
6246 indent(out, level) << "wasm::NoOpSorting" << print_info(this->sorting) << " (cumulative cost " << cost() << ')';
6247 this->child->print(out, level + 1);
6248}
6249
6250template<bool Predicated>
6251void Match<m::wasm::NestedLoopsJoin<Predicated>>::print(std::ostream &out, unsigned level) const
6252{
6253 indent(out, level) << "wasm::" << (Predicated ? "Predicated" : "") << "NestedLoopsJoin ";
6254 if (this->buffer_factory_ and this->join.schema().drop_constants().deduplicate().num_entries())
6255 out << "with " << this->buffer_num_tuples_ << " tuples output buffer ";
6256 out << this->join.schema() << print_info(this->join) << " (cumulative cost " << cost() << ')';
6257
6258 ++level;
6259 std::size_t i = this->children.size();
6260 while (i--) {
6261 const m::wasm::MatchBase &child = *this->children[i];
6262 indent(out, level) << i << ". input";
6263 child.print(out, level + 1);
6264 }
6265}
6266
6267template<bool Unique, bool Predicated>
6268void Match<m::wasm::SimpleHashJoin<Unique, Predicated>>::print(std::ostream &out, unsigned level) const
6269{
6270 indent(out, level) << "wasm::" << (Predicated ? "Predicated" : "") << "SimpleHashJoin";
6271 if (Unique) out << " on UNIQUE key ";
6272 if (this->buffer_factory_ and this->join.schema().drop_constants().deduplicate().num_entries())
6273 out << "with " << this->buffer_num_tuples_ << " tuples output buffer ";
6274 out << this->join.schema() << print_info(this->join) << " (cumulative cost " << cost() << ')';
6275
6276 ++level;
6277 const m::wasm::MatchBase &build = *this->children[0];
6278 const m::wasm::MatchBase &probe = *this->children[1];
6279 indent(out, level) << "probe input";
6280 probe.print(out, level + 1);
6281 indent(out, level) << "build input";
6282 build.print(out, level + 1);
6283}
6284
6285template<bool SortLeft, bool SortRight, bool Predicated, bool CmpPredicated>
6287 unsigned level) const
6288{
6289 indent(out, level) << "wasm::" << (Predicated ? "Predicated" : "") << "SortMergeJoin ";
6290 switch ((unsigned(SortLeft) << 1) | unsigned(SortRight))
6291 {
6292 case 0: out << "pre-sorted "; break;
6293 case 1: out << "sorting right input " << (CmpPredicated ? "predicated " : ""); break;
6294 case 2: out << "sorting left input " << (CmpPredicated ? "predicated " : ""); break;
6295 case 3: out << "sorting both inputs " << (CmpPredicated ? "predicated " : ""); break;
6296 }
6297 const bool needs_buffer_parent = not is<const ScanOperator>(this->parent) or SortLeft;
6298 const bool needs_buffer_child = not is<const ScanOperator>(this->child) or SortRight;
6299 if (needs_buffer_parent and needs_buffer_child)
6300 out << "and materializing both inputs ";
6301 else if (needs_buffer_parent)
6302 out << "and materializing left input ";
6303 else if (needs_buffer_child)
6304 out << "and materializing right input ";
6305 out << this->join.schema() << print_info(this->join) << " (cumulative cost " << cost() << ')';
6306
6307 ++level;
6308 const m::wasm::MatchBase &left = *this->children[0];
6309 const m::wasm::MatchBase &right = *this->children[1];
6310 indent(out, level) << "right input";
6311 right.print(out, level + 1);
6312 indent(out, level) << "left input";
6313 left.print(out, level + 1);
6314}
6315
6316void Match<m::wasm::Limit>::print(std::ostream &out, unsigned level) const
6317{
6318 indent(out, level) << "wasm::Limit " << this->limit.schema() << print_info(this->limit)
6319 << " (cumulative cost " << cost() << ')';
6320 this->child->print(out, level + 1);
6321}
6322
6323void Match<m::wasm::HashBasedGroupJoin>::print(std::ostream &out, unsigned level) const
6324{
6325 indent(out, level) << "wasm::HashBasedGroupJoin ";
6326 if (this->buffer_factory_ and this->grouping.schema().drop_constants().deduplicate().num_entries())
6327 out << "with " << this->buffer_num_tuples_ << " tuples output buffer ";
6328 out << this->grouping.schema() << print_info(this->grouping) << " (cumulative cost " << cost() << ')';
6329
6330 ++level;
6331 const m::wasm::MatchBase &build = *this->children[0];
6332 const m::wasm::MatchBase &probe = *this->children[1];
6333 indent(out, level) << "probe input";
6334 probe.print(out, level + 1);
6335 indent(out, level) << "build input";
6336 build.print(out, level + 1);
6337}
6338
6339
6340/*======================================================================================================================
6341 * ThePreOrderMatchBaseVisitor, ThePostOrderMatchBaseVisitor
6342 *====================================================================================================================*/
6343
6344namespace {
6345
6346template<bool C, bool PreOrder>
6347struct recursive_matchbase_visitor : TheRecursiveMatchBaseVisitorBase<C>
6348{
6350 template<typename T> using Const = typename super::template Const<T>;
6351 using callback_t = std::conditional_t<C, ConstMatchBaseVisitor, MatchBaseVisitor>;
6352
6353 private:
6354 callback_t &callback_;
6355
6356 public:
6357 recursive_matchbase_visitor(callback_t &callback) : callback_(callback) { }
6358
6359 using super::operator();
6360#define DECLARE(CLASS) \
6361 void operator()(Const<CLASS> &M) override { \
6362 if constexpr (PreOrder) try { callback_(M); } catch (visit_skip_subtree) { return; } \
6363 super::operator()(M); \
6364 if constexpr (not PreOrder) callback_(M); \
6365 }
6367#undef DECLARE
6368};
6369
6370}
6371
6372template<bool C>
6374{
6375 recursive_matchbase_visitor<C, /* PreOrder= */ true>{*this}(M);
6376}
6377
6378template<bool C>
6380{
6381 recursive_matchbase_visitor<C, /* PreOrder= */ false>{*this}(M);
6382}
6383
6384
6385/*======================================================================================================================
6386 * Explicit template instantiations
6387 *====================================================================================================================*/
6388
6389#define INSTANTIATE(CLASS) \
6390 template struct m::wasm::CLASS; \
6391 template struct m::Match<m::wasm::CLASS>;
6393#undef INSTANTIATE
6394
__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.
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.
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_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 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:1985
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:5784
typename detail::var_helper< T >::type Var
Local variable.
Definition: WasmDSL.hpp:5779
auto make_signed()
Conversion of a PrimitiveExpr<T, L> to a PrimitiveExpr<std::make_signed_t<T>, L>.
Definition: WasmDSL.hpp:3650
void GOTO(const Block &block)
Jumps to the end of block.
Definition: WasmDSL.hpp:6199
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:1519
std::size_t L
Definition: WasmDSL.hpp:528
typename detail::global_helper< T >::type Global
Global variable.
Definition: WasmDSL.hpp:5789
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:6215
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:2504
Bool< L > uint8_t n
Definition: WasmUtil.hpp:1318
void discard()
Discards this.
Definition: WasmDSL.hpp:1588
PrimitiveExpr< uint64_t, L > L L L L U
Definition: WasmDSL.hpp:2352
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:3696
auto op
Definition: WasmDSL.hpp:2384
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:1617
PrimitiveExpr clone() const
Creates and returns a deep copy of this.
Definition: WasmDSL.hpp:1577
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:2474
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:1466
‍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:53
A recursive model index with two layers consiting only of linear monels that maps keys to their tuple...
Definition: Index.hpp:137
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:1005
void attach_to_current()
Attaches this Block to the wasm::Block currently active in the Module.
Definition: WasmDSL.hpp:1084
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:1367
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:6709
static Module & Get()
Definition: WasmDSL.hpp:714
friend struct Allocator
Definition: WasmDSL.hpp:652
static unsigned ID()
Returns the ID of the current module.
Definition: WasmDSL.hpp:721
void emit_call(const char *fn, PrimitiveExpr< ParamTypes, ParamLs >... args)
Definition: WasmDSL.hpp:6716
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)