25static
void add_wasm_operator_args()
33 "--scan-implementations",
34 "a comma seperated list of physical scan implementations to consider (`Scan` or `IndexScan`)",
35 [](std::vector<std::string_view> impls){
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;
43 std::cerr <<
"warning: ignore invalid physical scan implementation " << elem << std::endl;
50 "--grouping-implementations",
51 "a comma seperated list of physical grouping implementations to consider (`HashBased` or "
53 [](std::vector<std::string_view> impls){
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;
61 std::cerr <<
"warning: ignore invalid physical grouping implementation " << elem << std::endl;
68 "--sorting-implementations",
69 "a comma seperated list of physical sorting implementations to consider (`Quicksort` or "
71 [](std::vector<std::string_view> impls){
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;
79 std::cerr <<
"warning: ignore invalid physical sorting implementation " << elem << std::endl;
86 "--join-implementations",
87 "a comma seperated list of physical join implementations to consider (`NestedLoops`, "
88 "`SimpleHash`, or `SortMerge`)",
89 [](std::vector<std::string_view> impls){
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;
99 std::cerr <<
"warning: ignore invalid physical join implementation " << elem << std::endl;
106 "--index-implementations",
107 "a comma separated list of index implementations to consider for index scans (`Array`, or"
109 [](std::vector<std::string_view> impls){
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;
117 std::cerr <<
"warning: ignore invalid index implementation " << elem << std::endl;
124 "--index-scan-strategy",
125 "specify the index scan strategy (`Compilation`, `Interpretation`, or `Hybrid`)",
126 [](
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;
134 std::cerr <<
"warning: ignore invalid index scan strategy " << strategy << std::endl;
140 "--index-scan-compilation-strategy",
141 "specify the materialization strategy for index scans (`Callback` or `ExposedMemory`)",
142 [](
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;
148 std::cerr <<
"warning: ignore invalid index scan strategy " << strategy << std::endl;
154 "--index-scan-materialization-strategy",
155 "specify the materialization strategy for index scans (`Inline` or `Memory`)",
156 [](
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;
162 std::cerr <<
"warning: ignore invalid index scan strategy " << strategy << std::endl;
168 "--filter-selection-strategy",
169 "specify the selection strategy for filters (`Branching` or `Predicated`)",
170 [](
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;
176 std::cerr <<
"warning: ignore invalid filter selection strategy " << strategy << std::endl;
182 "--quicksort-cmp-selection-strategy",
183 "specify the selection strategy for comparisons in quicksort (`Branching` or `Predicated`)",
184 [](
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;
190 std::cerr <<
"warning: ignore invalid quicksort comparison selection strategy " << strategy << std::endl;
196 "--nested-loops-join-selection-strategy",
197 "specify the selection strategy for nested-loops joins (`Branching` or `Predicated`)",
198 [](
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;
204 std::cerr <<
"warning: ignore invalid nested-loops join selection strategy " << strategy << std::endl;
210 "--simple-hash-join-selection-strategy",
211 "specify the selection strategy for simple hash joins (`Branching` or `Predicated`)",
212 [](
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;
218 std::cerr <<
"warning: ignore invalid simple hash join selection strategy " << strategy << std::endl;
224 "--simple-hash-join-ordering-strategy",
225 "specify the ordering strategy for simple hash joins (`BuildOnLeft` or `BuildOnRight`)",
226 [](
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;
232 std::cerr <<
"warning: ignore invalid simple hash join ordering strategy " << strategy << std::endl;
238 "--sort-merge-join-selection-strategy",
239 "specify the selection strategy for sort merge joins (`Branching` or `Predicated`)",
240 [](
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;
246 std::cerr <<
"warning: ignore invalid sort merge join selection strategy " << strategy << std::endl;
252 "--sort-merge-join-cmp-selection-strategy",
253 "specify the selection strategy for comparisons while sorting in sort merge joins "
254 "(`Branching` or `Predicated`)",
255 [](
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;
261 std::cerr <<
"warning: ignore invalid sort merge join comparison selection strategy " << strategy
268 "--hash-table-implementation",
269 "specify the hash table implementation (`OpenAddressing` or `Chained`)",
270 [](
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;
276 std::cerr <<
"warning: ignore invalid hash table implementation " << impl << std::endl;
282 "--hash-table-probing-strategy",
283 "specify the probing strategy for hash tables (`Linear` or `Quadratic`)",
284 [](
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;
290 std::cerr <<
"warning: ignore invalid hash table probing strategy " << strategy << std::endl;
296 "--hash-table-storing-strategy",
297 "specify the storing strategy for hash tables (`InPlace` or `OutOfPlace`)",
298 [](
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;
304 std::cerr <<
"warning: ignore invalid hash table storing strategy " << strategy << std::endl;
310 "--hash-table-max-load-factor",
311 "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 [](
double load_factor){
314 options::load_factor_open_addressing = load_factor;
315 options::load_factor_chained = load_factor;
321 "--hash-table-initial-capacity",
322 "specify the initial capacity for hash tables",
323 [](uint64_t initial_capacity){
324 options::hash_table_initial_capacity = initial_capacity;
330 "--no-hash-based-group-join",
331 "disable potential use of hash-based group-join",
332 [](
bool){ options::hash_based_group_join =
false; }
337 "--hard-pipeline-breaker-layout",
338 "specify the layout for hard pipeline breakers (`Row`, `PAX4K`, `PAX64K`, `PAX512K`, "
339 "`PAX4M`, or `PAX64M`)",
340 [](
const char *layout){
341 if (
streq(layout,
"Row")) {
342 options::hard_pipeline_breaker_layout = std::make_unique<RowLayoutFactory>();
346 if (
streq(layout,
"PAX4K")) {
348 block_size = 1UL << 12;
349 }
else if (
streq(layout,
"PAX64K")) {
351 block_size = 1UL << 16;
352 }
else if (
streq(layout,
"PAX512K")) {
354 block_size = 1UL << 19;
355 }
else if (
streq(layout,
"PAX4M")) {
357 block_size = 1UL << 22;
358 }
else if (
streq(layout,
"PAX64M")) {
360 block_size = 1UL << 26;
361 }
else if (
streq(layout,
"PAX16Tup")) {
364 }
else if (
streq(layout,
"PAX128Tup")) {
367 }
else if (
streq(layout,
"PAX1024Tup")) {
371 std::cerr <<
"warning: ignore invalid layout for hard pipeline breakers " << layout << std::endl;
373 options::hard_pipeline_breaker_layout = std::make_unique<PAXLayoutFactory>(size_type, block_size);
380 "--soft-pipeline-breaker",
381 "a comma seperated list where to insert soft pipeline breakers (`AfterAll`, `AfterScan`, "
382 "`AfterFilter`, `AfterProjection`, `AfterNestedLoopsJoin`, or `AfterSimpleHashJoin`)",
383 [](std::vector<std::string_view> location){
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;
401 std::cerr <<
"warning: ignore invalid location for soft pipeline breakers " << elem << std::endl;
408 "--soft-pipeline-breaker-layout",
409 "specify the layout for soft pipeline breakers (`Row`, `PAX4K`, `PAX64K`, `PAX512K`, "
410 "`PAX4M`, or `PAX64M`)",
411 [](
const char *layout){
412 if (
streq(layout,
"Row")) {
413 options::soft_pipeline_breaker_layout = std::make_unique<RowLayoutFactory>();
417 if (
streq(layout,
"PAX4K")) {
419 block_size = 1UL << 12;
420 }
else if (
streq(layout,
"PAX64K")) {
422 block_size = 1UL << 16;
423 }
else if (
streq(layout,
"PAX512K")) {
425 block_size = 1UL << 19;
426 }
else if (
streq(layout,
"PAX4M")) {
428 block_size = 1UL << 22;
429 }
else if (
streq(layout,
"PAX64M")) {
431 block_size = 1UL << 26;
432 }
else if (
streq(layout,
"PAX16Tup")) {
435 }
else if (
streq(layout,
"PAX128Tup")) {
438 }
else if (
streq(layout,
"PAX1024Tup")) {
442 std::cerr <<
"warning: ignore invalid layout for soft pipeline breakers " << layout << std::endl;
444 options::soft_pipeline_breaker_layout = std::make_unique<PAXLayoutFactory>(size_type, block_size);
451 "--soft-pipeline-breaker-num-tuples",
452 "set the size in tuples for soft pipeline breakers (0 means infinite)",
453 [](std::size_t num_tuples){ options::soft_pipeline_breaker_num_tuples = num_tuples; }
458 "--index-sequential-scan-batch-size",
459 "set the number of tuples ids communicated between host and V8 per batch during index "
461 "(0 means infinite), ignored in case of --isam-compile-qualifying",
462 [](std::size_t size){ options::index_sequential_scan_batch_size = size; }
467 "--result-set-window-size",
468 "set the window size in tuples for the result set (0 means infinite)",
469 [](std::size_t size){ options::result_set_window_size = size; }
474 "--no-exploit-unique-build",
475 "disable potential exploitation of uniqueness of build key in hash joins",
476 [](
bool){ options::exploit_unique_build =
false; }
482 "disable potential use of SIMDfication",
483 [](
bool){ options::simd =
false; }
488 "--no-double-pumping",
489 "disable use of double pumping (has only an effect if SIMDfication is enabled)",
490 [](
bool){ options::double_pumping =
false; }
496 "set the number of SIMD lanes to prefer",
497 [](std::size_t lanes){ options::simd_lanes = lanes; }
502 "--xxx-asc-sorted-attributes",
503 "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 "
506 [&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;
512 options::sorted_attributes.emplace_back(std::move(attr),
true);
519 "--xxx-desc-sorted-attributes",
520 "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 "
523 [&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;
529 options::sorted_attributes.emplace_back(std::move(attr),
false);
549 if (
bool(options::scan_implementations bitand option_configs::ScanImplementation::SCAN)) {
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))
560 if (
bool(options::filter_selection_strategy bitand option_configs::SelectionStrategy::BRANCHING))
562 if (
bool(options::filter_selection_strategy bitand option_configs::SelectionStrategy::PREDICATED))
566 if (
bool(options::grouping_implementations bitand option_configs::GroupingImplementation::HASH_BASED))
568 if (
bool(options::grouping_implementations bitand option_configs::GroupingImplementation::ORDERED))
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))
577 if (
bool(options::sorting_implementations bitand option_configs::SortingImplementation::NOOP))
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))
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)
591 if (
bool(options::simple_hash_join_selection_strategy bitand option_configs::SelectionStrategy::PREDICATED)) {
593 if (options::exploit_unique_build)
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)) {
605 if (
bool(options::sort_merge_join_cmp_selection_strategy bitand option_configs::SelectionStrategy::PREDICATED)) {
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)) {
619 if (
bool(options::sort_merge_join_cmp_selection_strategy bitand option_configs::SelectionStrategy::PREDICATED)) {
628 if (options::hash_based_group_join)
649 context.result_set_factory = factory.
clone();
652 if (window_size != 0) {
656 std::optional<Var<U64x1>> counter;
661 FUNCTION(child_pipeline,
void(
void))
673 "SIMDfication with predication not supported");
674 *counter += env.extract_predicate<_Boolx1>().is_true_and_not_null().to<uint64_t>();
680 IF (*counter == window_size) {
688 counter_backup = *counter;
702 FUNCTION(child_pipeline,
void(
void))
706 std::optional<Var<U64x1>> num_tuples;
716 "SIMDfication with predication not supported");
717 *num_tuples += env.extract_predicate<_Boolx1>().is_true_and_not_null().to<uint64_t>();
735 if (window_size != 0) {
740 GlobalBuffer result_set(schema, factory,
false, window_size);
743 FUNCTION(child_pipeline,
void(
void))
759 IF (single_slot_free
and result_set.
size() == 0
U) {
780 FUNCTION(child_pipeline,
void(
void))
786 [&](){ result_set.
consume(); },
806 const std::vector<std::unique_ptr<ast::Expr>> &
args;
823std::pair<std::vector<aggregate_info_t>, std::unordered_map<Schema::Identifier, avg_aggregate_info_t>>
825 const Schema &schema, std::size_t aggregates_offset = 0)
827 std::vector<aggregate_info_t> aggregates_info;
828 std::unordered_map<Schema::Identifier, avg_aggregate_info_t> avg_aggregates_info;
830 for (std::size_t i = aggregates_offset; i < schema.
num_entries(); ++i) {
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())
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");
841 if (fn.fnid == m::Function::FN_AVG) {
842 M_insist(fn_expr.args.size() == 1,
"AVG aggregate function expects exactly one argument");
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];
851 std::optional<Schema::Identifier> running_count;
852 if (
auto it = std::find_if(aggregates.cbegin(), aggregates.cend(), pred);
853 it != aggregates.cend())
855 const auto idx_agg = std::distance(aggregates.cbegin(), it);
856 running_count = schema[aggregates_offset + idx_agg].id;
858 std::ostringstream oss;
859 oss <<
"$running_count_" << fn_expr;
863 .fnid = m::Function::FN_COUNT,
869 std::optional<Schema::Identifier> sum;
870 bool compute_running_avg;
871 if (fn_expr.args[0]->type()->size() <= 32) {
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];
882 if (
auto it = std::find_if(aggregates.cbegin(), aggregates.cend(), pred);
883 it != aggregates.cend())
885 const auto idx_agg = std::distance(aggregates.cbegin(), it);
886 sum = schema[aggregates_offset + idx_agg].id;
888 std::ostringstream oss;
889 oss <<
"$sum_" << fn_expr;
892 switch (as<const Numeric>(*fn_expr.args[0]->type()).kind) {
894 case Numeric::N_Decimal:
897 case Numeric::N_Float:
901 .
entry = { *sum, type, e.constraints },
902 .fnid = m::Function::FN_SUM,
909 compute_running_avg =
true;
913 .fnid = m::Function::FN_AVG,
920 .running_count = std::move(*running_count),
921 .sum = std::move(*sum),
922 .compute_running_avg = compute_running_avg
933 return { std::move(aggregates_info), std::move(avg_aggregates_info) };
939std::pair<std::vector<Schema::Identifier>, std::vector<Schema::Identifier>>
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());
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");
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));
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) };
964 static std::ostringstream oss;
966 oss << table_name <<
"_num_rows";
972 static std::ostringstream oss;
974 oss << table_name <<
"_mem";
980 static std::ostringstream oss;
982 oss <<
"index_" << table_name <<
'_' << attr_name <<
"_array" <<
"_num_entries";
988 static std::ostringstream oss;
990 oss <<
"index_" << table_name <<
'_' << attr_name <<
"_array" <<
"_mem";
997 uint64_t initial_capacity;
998 if (options::hash_table_initial_capacity) {
999 initial_capacity = *options::hash_table_initial_capacity;
1002 initial_capacity =
static_cast<uint64_t
>(std::ceil(
op.info().estimated_cardinality / load_factor));
1003 else if (
auto scan = cast<const ScanOperator>(&
op))
1004 initial_capacity =
static_cast<uint64_t
>(std::ceil(scan->store().num_rows() / load_factor));
1006 initial_capacity = 1024;
1008 return initial_capacity;
1015 std::optional<std::reference_wrapper<const ast::Expr>>
lo,
hi;
1021 if (is<const Constant>(
expr))
1023 if (
auto u = cast<const UnaryExpr>(&
expr)) {
1025 if ((u->op().type == TK_MINUS or u->op().type == TK_PLUS)
and is<const Constant>(*u->expr))
1035 if (
auto c = cast<const Constant>(&
expr))
1036 return { *c,
false };
1037 auto &u = as<const UnaryExpr>(
expr);
1038 return { as<const Constant>(*u.expr), u.op().type == TK_MINUS };
1048 M_insist(not cnf.empty(),
"filter condition must not be empty");
1050 M_insist(designators.num_entries() == 1,
"filter condition must contain exactly one designator");
1053 for (
auto &clause : cnf) {
1054 M_insist(clause.size() == 1,
"invalid predicate");
1055 auto &literal = clause[0];
1056 auto &
binary = as<const BinaryExpr>(literal.expr());
1059 bool has_attribute_left = is<const Designator>(
binary.lhs);
1060 auto &bound = has_attribute_left ? *
binary.rhs : *
binary.lhs;
1062 switch(
binary.tok.type) {
1066 M_insist(not
bool(bounds.
lo)
and not
bool(bounds.
hi),
"bound already set");
1067 bounds.
lo = bounds.
hi = std::cref(bound);
1071 if (has_attribute_left) {
1072 M_insist(not
bool(bounds.
lo),
"lo bound already set");
1073 bounds.
lo = std::cref(bound);
1076 M_insist(not
bool(bounds.
hi),
"hi bound already set");
1077 bounds.
hi = std::cref(bound);
1081 case TK_GREATER_EQUAL:
1082 if (has_attribute_left) {
1083 M_insist(not
bool(bounds.
lo),
"lo bound already set");
1084 bounds.
lo = std::cref(bound);
1087 M_insist(not
bool(bounds.
hi),
"hi bound already set");
1088 bounds.
hi = std::cref(bound);
1093 if (has_attribute_left) {
1094 M_insist(not
bool(bounds.
hi),
"hi bound already set");
1095 bounds.
hi = std::cref(bound);
1098 M_insist(not
bool(bounds.
lo),
"lo bound already set");
1099 bounds.
lo = std::cref(bound);
1104 if (has_attribute_left) {
1105 M_insist(not
bool(bounds.
hi),
"hi bound already set");
1106 bounds.
hi = std::cref(bound);
1109 M_insist(not
bool(bounds.
lo),
"lo bound already set");
1110 bounds.
lo = std::cref(bound);
1116 M_insist(
bool(bounds.
lo) or
bool(bounds.
hi),
"either bound must be set");
1127 std::optional<Var<U64x1>> num_tuples;
1137 *num_tuples += env.extract_predicate<_Boolx1>().is_true_and_not_null().to<uint64_t>();
1141 auto pred = env.extract_predicate<_Boolx16>().is_true_and_not_null();
1142 *num_tuples += pred.bitmask().popcnt();
1163template<
bool SIMDfied>
1170 if constexpr (SIMDfied) {
1181template<
bool SIMDfied>
1184 M_insist(
bool(M.result_set_factory),
"`wasm::Callback` must have a factory for the result set");
1186 auto result_set_schema = M.callback.schema().drop_constants().deduplicate();
1187 write_result_set(result_set_schema, *M.result_set_factory, M.result_set_window_size, *M.child);
1195template<
bool SIMDfied>
1202 if constexpr (SIMDfied) {
1213template<
bool SIMDfied>
1216 M_insist(
bool(M.result_set_factory),
"`wasm::Print` must have a factory for the result set");
1218 auto result_set_schema = M.print_op.schema().drop_constants().deduplicate();
1219 write_result_set(result_set_schema, *M.result_set_factory, M.result_set_window_size, *M.child);
1227template<
bool SIMDfied>
1229 const std::tuple<const ScanOperator*> &partial_inner_nodes)
1235 if constexpr (SIMDfied) {
1236 auto &scan = *std::get<0>(partial_inner_nodes);
1237 auto &table = scan.store().table();
1240 if (not supports_simd(table.layout(), table.schema(scan.alias()), scan.schema()))
1244 if (scan.store().num_rows() %
get_num_simd_lanes(table.layout(), table.schema(scan.alias()), scan.schema()) != 0)
1251template<
bool SIMDfied>
1259 if constexpr (SIMDfied) {
1261 auto &table = M.scan.store().table();
1271 for (
auto &e : M.scan.schema()) {
1272 auto pred = [&e](
const auto &p){
return e.id == p.first; };
1279 if (not orders.
empty())
1285template<
bool SIMDfied>
1288 auto &schema = M.scan.schema();
1289 auto &table = M.scan.store().table();
1291 M_insist(schema == schema.drop_constants().deduplicate(),
"schema of `ScanOperator` must not contain NULL or duplicates");
1292 M_insist(not table.layout().is_finite(),
"layout for `wasm::Scan` must be infinite");
1299 const auto num_simd_lanes_preferred =
1311 if (schema.num_entries() == 0) {
1313 WHILE (tuple_id < num_rows) {
1328 static Schema empty_schema;
1333 inits.attach_to_current();
1334 WHILE (tuple_id < num_rows) {
1335 loads.attach_to_current();
1337 jumps.attach_to_current();
1349template<
idx::IndexMethod IndexMethod>
1357 auto &filter = *std::get<0>(partial_inner_nodes);
1358 auto &cnf = filter.filter();
1360 M_insist(not cnf.empty(),
"Filter condition must not be empty");
1362 auto &scan = *std::get<1>(partial_inner_nodes);
1363 auto &table = scan.store().table();
1369 std::vector<Schema::Identifier> ids;
1370 for (
auto &entry : cnf.get_required()) {
1372 M_insist(table.name() ==
id.prefix,
"Table name should match designator table name");
1375 if (DB.has_index(table.name(),
id.name, IndexMethod))
1393 bool has_lo_bound =
false;
1394 bool has_hi_bound =
false;
1395 for (
auto &clause : cnf) {
1396 if (clause.size() != 1)
1399 auto &predicate = clause[0];
1400 if (predicate.negative())
1403 auto expr = cast<const BinaryExpr>(&predicate.expr());
1407 bool has_attribute_left = is<const Designator>(
expr->lhs);
1408 auto &attribute = has_attribute_left ? *
expr->lhs : *
expr->rhs;
1409 auto &constant = has_attribute_left ? *
expr->rhs : *
expr->lhs;
1410 if (not is<const Designator>(attribute))
1415 switch(
expr->tok.type) {
1419 if (not has_lo_bound
and not has_hi_bound) {
1420 has_lo_bound = has_hi_bound =
true;
1426 case TK_GREATER_EQUAL:
1427 if (has_attribute_left
and not has_lo_bound) {
1428 has_lo_bound =
true;
1429 }
else if (not has_attribute_left
and not has_hi_bound) {
1430 has_hi_bound =
true;
1437 if (has_attribute_left
and not has_hi_bound) {
1438 has_hi_bound =
true;
1439 }
else if (not has_attribute_left
and not has_lo_bound) {
1440 has_lo_bound =
true;
1450template<
idx::IndexMethod IndexMethod>
1463 auto &cnf = M.filter.filter();
1464 Schema designators = cnf.get_required();
1465 M_insist(designators.
num_entries() == 1,
"filter condition must contain exactly one designator");
1476template<
idx::IndexMethod IndexMethod>
1483template<
idx::IndexMethod IndexMethod,
typename Index, sql_type SqlT>
1487 using key_type = Index::key_type;
1488 using value_type = Index::value_type;
1489 constexpr uint64_t entry_size =
sizeof(
typename Index::entry_type);
1490 using sql_type = SqlT;
1492 auto table_name = M.scan.store().table().name();
1497 const char *scan_fn, *lower_bound_fn, *upper_bound_fn;
1498#define SET_CALLBACK_FNS(INDEX, KEY) \
1499 scan_fn = M_STR(idx_scan_##INDEX##_##KEY); \
1500 lower_bound_fn = M_STR(idx_lower_bound_##INDEX##_##KEY); \
1501 upper_bound_fn = M_STR(idx_upper_bound_##INDEX##_##KEY)
1503#define RESOLVE_KEYTYPE(INDEX) \
1504 if constexpr(std::same_as<SqlT, _Boolx1>) { \
1505 SET_CALLBACK_FNS(INDEX, b); \
1506 } else if constexpr(std::same_as<sql_type, _I8x1>) { \
1507 SET_CALLBACK_FNS(INDEX, i1); \
1508 } else if constexpr(std::same_as<sql_type, _I16x1>) { \
1509 SET_CALLBACK_FNS(INDEX, i2); \
1510 } else if constexpr(std::same_as<sql_type, _I32x1>) { \
1511 SET_CALLBACK_FNS(INDEX, i4); \
1512 } else if constexpr(std::same_as<sql_type, _I64x1>) { \
1513 SET_CALLBACK_FNS(INDEX, i8); \
1514 } else if constexpr(std::same_as<sql_type, _Floatx1>) { \
1515 SET_CALLBACK_FNS(INDEX, f); \
1516 } else if constexpr(std::same_as<sql_type, _Doublex1>) { \
1517 SET_CALLBACK_FNS(INDEX, d); \
1518 } else if constexpr(std::same_as<sql_type, NChar>) { \
1519 SET_CALLBACK_FNS(IDNEX, p); \
1521 M_unreachable("incompatible SQL type"); \
1523 if constexpr(is_specialization<Index, idx::ArrayIndex>) {
1525 }
else if constexpr(is_specialization<Index, idx::RecursiveModelIndex>) {
1530#undef RESOLVE_KEYTYPE
1531#undef SET_CALLBACK_FNS
1535 U64x1 index_id(context.add_index(index));
1538 auto compile_bound_lookup = [&](
const ast::Expr &bound,
bool is_lower_bound) {
1540 auto c = Interpreter::eval(constant);
1544 M_insist(not is_negative,
"boolean cannot be negative");
1546 auto i64 = int64_t(c);
1547 M_insist(std::in_range<key_type>(i64),
"integral constant must be in range");
1548 _key = key_type(i64);
1549 _key = is_negative ? -_key : _key;
1550 }
else if constexpr(std::same_as<float, key_type>) {
1553 M_insist(_key == d,
"downcasting should not impact precision");
1554 _key = is_negative ? -_key : _key;
1555 }
else if constexpr(std::same_as<double, key_type>) {
1557 _key = is_negative ? -_key : _key;
1558 }
else if constexpr(std::same_as<const char*, key_type>) {
1559 _key =
reinterpret_cast<const char*
>(c.as_p());
1560 M_insist(not is_negative,
"string cannot be negative");
1563 std::optional<typename sql_type::primitive_type> key;
1565 if constexpr (std::same_as<sql_type, NChar>) {
1574 if constexpr (std::same_as<sql_type, NChar>) {
1579 key.emplace(U64x1(*key_ptr).to<
char*>());
1582 *key_address = _key;
1585 key.emplace(*key_ptr);
1590 M_insist(
bool(key),
"key must be set");
1592 is_lower_bound ? lower_bound_fn : upper_bound_fn,
1600 : U64x1(index.num_entries()));
1604 M_insist(std::in_range<uint64_t>(M.batch_size),
"should fit in uint64_t");
1609 U64x1 num_results = hi - lo;
1610 U64x1 num_results_cpy = num_results.clone();
1611 U64x1 batch_size = M.batch_size == 0 ? num_results.clone() : U64x1(M.batch_size);
1612 U64x1 batch_size_cpy = batch_size.clone();
1613 return Select(batch_size < num_results, batch_size_cpy, num_results_cpy);
1624 num_tuples_in_batch =
Select(hi - lo > alloc_size, alloc_size, hi - lo);
1630 buffer_address.clone(),
1631 num_tuples_in_batch.val()
1633 lo += num_tuples_in_batch;
1634 ptr = buffer_address.clone();
1635 WHILE (num_tuples_in_batch > 0
U) {
1636 static Schema empty_schema;
1641 M.scan.store().table().layout(),
1642 M.scan.store().table().schema(M.scan.alias()),
1646 num_tuples_in_batch -= 1U;
1655 IF (alloc_size > U64x1(0)) {
1660 auto compile_bound_lookup = [&](
const ast::Expr &bound,
bool is_lower_bound) {
1662 auto c = Interpreter::eval(constant);
1666 M_insist(not is_negative,
"boolean cannot be negative");
1668 auto i64 = int64_t(c);
1669 M_insist(std::in_range<key_type>(i64),
"integral constant must be in range");
1670 _key = key_type(i64);
1671 _key = is_negative ? -_key : _key;
1672 }
else if constexpr(std::same_as<float, key_type>) {
1675 M_insist(_key == d,
"downcasting should not impact precision");
1676 _key = is_negative ? -_key : _key;
1677 }
else if constexpr(std::same_as<double, key_type>) {
1679 _key = is_negative ? -_key : _key;
1680 }
else if constexpr(std::same_as<const char*, key_type>) {
1681 _key =
reinterpret_cast<const char*
>(c.as_p());
1682 M_insist(not is_negative,
"string cannot be negative");
1685 std::optional<typename sql_type::primitive_type> key;
1687 if constexpr (std::same_as<sql_type, NChar>) {
1696 if constexpr (std::same_as<sql_type, NChar>) {
1701 key.emplace(U64x1(*key_ptr).to<
char*>());
1704 *key_address = _key;
1707 key.emplace(*key_ptr);
1712 M_insist(
bool(key),
"key must be set");
1725 NChar(it.to<
char*>(),
false, as<const CharacterSequence>(bound.
type())),
1726 NChar(*key,
false, as<const CharacterSequence>(bound.
type())),
1727 is_lower_bound ?
LT :
LE
1728 ).insist_not_null(),
1730 std::same_as<
M_COMMA(key_type)
bool>,
1731 is_lower_bound ? Boolx1(*it.to<
bool*>()).to<uint8_t>() < (*key).template to<uint8_t>()
1732 : Boolx1(*it.to<
bool*>()).to<uint8_t>() <= (*key).template to<uint8_t>(),
1733 is_lower_bound ? *it.to<key_type*>() < *key : *it.to<key_type*>() <= *key
1737 first = it + entry_size;
1761 constexpr int64_t value_offset =
1762 ((
sizeof(key_type) +
alignof(value_type) - 1U) /
alignof(value_type)) *
alignof(value_type);
1764 static Schema empty_schema;
1769 M.scan.store().table().layout(),
1770 M.scan.store().table().schema(M.scan.alias()),
1774 lo += int64_t(entry_size);
1784template<
idx::IndexMethod IndexMethod,
typename Index>
1789 using key_type = Index::key_type;
1791 static Schema empty_schema;
1794 auto interpret_and_lookup_bound = [&](
const ast::Expr &bound,
bool is_lower_bound) -> std::size_t {
1796 auto c = Interpreter::eval(constant);
1800 M_insist(not is_negative,
"boolean cannot be negative");
1802 auto i64 = int64_t(c);
1803 M_insist(std::in_range<key_type>(i64),
"integral constant must be in range");
1804 key = key_type(i64);
1805 key = is_negative ? -key : key;
1806 }
else if constexpr(std::same_as<float, key_type>) {
1809 M_insist(key == d,
"downcasting should not impact precision");
1810 key = is_negative ? -key : key;
1811 }
else if constexpr(std::same_as<double, key_type>) {
1813 key = is_negative ? -key : key;
1814 }
else if constexpr(std::same_as<const char*, key_type>) {
1815 key =
reinterpret_cast<const char*
>(c.as_p());
1816 M_insist(not is_negative,
"string cannot be negative");
1818 return std::distance(index.begin(), is_lower_bound ? index.lower_bound(key)
1819 : index.upper_bound(key));
1824 : index.num_entries();
1825 M_insist(lo <= hi,
"bounds need to be valid");
1829 uint64_t num_results = hi - lo;
1830 uint64_t *buffer_address =
Module::Allocator().raw_malloc<uint64_t>(num_results + 1);
1833 uint64_t *buffer_ptr = buffer_address;
1834 *buffer_ptr = num_results;
1836 for (
auto it = index.begin() + lo; it != index.begin() + hi; ++it) {
1837 M_insist(std::in_range<uint64_t>(it->second),
"tuple id must fit in uint64_t");
1838 *buffer_ptr = it->second;
1854 M.scan.store().table().layout(),
1855 M.scan.store().table().schema(M.scan.alias()),
1866 FUNCTION(index_scan_parent_pipeline,
void(uint64_t))
1878 M.scan.store().table().layout(),
1879 M.scan.store().table().schema(M.scan.alias()),
1891 for (
auto it = index.begin() + lo; it != index.begin() + hi; ++it) {
1892 M_insist(std::in_range<uint64_t>(it->second),
"tuple id must fit in uint64_t");
1893 index_scan_parent_pipeline(uint64_t(it->second));
1900template<
idx::IndexMethod IndexMethod,
typename Index, sql_type SqlT>
1905 using key_type = Index::key_type;
1906 using value_type = Index::value_type;
1907 constexpr uint64_t entry_size =
sizeof(
typename Index::entry_type);
1908 using sql_type = SqlT;
1910 auto table_name = M.scan.store().table().name();
1914 const char *scan_fn;
1915#define SET_CALLBACK_FNS(INDEX, KEY) \
1916 scan_fn = M_STR(idx_scan_##INDEX##_##KEY)
1918#define RESOLVE_KEYTYPE(INDEX) \
1919 if constexpr(std::same_as<SqlT, _Boolx1>) { \
1920 SET_CALLBACK_FNS(INDEX, b); \
1921 } else if constexpr(std::same_as<sql_type, _I8x1>) { \
1922 SET_CALLBACK_FNS(INDEX, i1); \
1923 } else if constexpr(std::same_as<sql_type, _I16x1>) { \
1924 SET_CALLBACK_FNS(INDEX, i2); \
1925 } else if constexpr(std::same_as<sql_type, _I32x1>) { \
1926 SET_CALLBACK_FNS(INDEX, i4); \
1927 } else if constexpr(std::same_as<sql_type, _I64x1>) { \
1928 SET_CALLBACK_FNS(INDEX, i8); \
1929 } else if constexpr(std::same_as<sql_type, _Floatx1>) { \
1930 SET_CALLBACK_FNS(INDEX, f); \
1931 } else if constexpr(std::same_as<sql_type, _Doublex1>) { \
1932 SET_CALLBACK_FNS(INDEX, d); \
1933 } else if constexpr(std::same_as<sql_type, NChar>) { \
1934 SET_CALLBACK_FNS(IDNEX, p); \
1936 M_unreachable("incompatible SQL type"); \
1938 if constexpr(is_specialization<Index, idx::ArrayIndex>) {
1940 }
else if constexpr(is_specialization<Index, idx::RecursiveModelIndex>) {
1945#undef RESOLVE_KEYTYPE
1946#undef SET_CALLBACK_FNS
1950 U64x1 index_id(context.add_index(index));
1953 auto interpret_and_lookup_bound = [&](
const ast::Expr &bound,
bool is_lower_bound) -> std::size_t {
1955 auto c = Interpreter::eval(constant);
1959 M_insist(not is_negative,
"boolean cannot be negative");
1961 auto i64 = int64_t(c);
1962 M_insist(std::in_range<key_type>(i64),
"integral constant must be in range");
1963 key = key_type(i64);
1964 key = is_negative ? -key : key;
1965 }
else if constexpr(std::same_as<float, key_type>) {
1968 M_insist(key == d,
"downcasting should not impact precision");
1969 key = is_negative ? -key : key;
1970 }
else if constexpr(std::same_as<double, key_type>) {
1972 key = is_negative ? -key : key;
1973 }
else if constexpr(std::same_as<const char*, key_type>) {
1974 key =
reinterpret_cast<const char*
>(c.as_p());
1975 M_insist(not is_negative,
"string cannot be negative");
1977 return std::distance(index.begin(), is_lower_bound ? index.lower_bound(key)
1978 : index.upper_bound(key));
1983 : index.num_entries();
1984 M_insist(lo <= hi,
"bounds need to be valid");
1985 M_insist(std::in_range<uint64_t>(lo),
"should fit in uint64_t");
1986 M_insist(std::in_range<uint64_t>(hi),
"should fit in uint64_t");
1989 std::optional<U64x1> begin;
1990 std::optional<U64x1> end;
1999 offset_address[0] = uint64_t(lo);
2000 offset_address[1] = uint64_t(hi);
2003 begin.emplace(*offset_ptr.clone());
2004 end.emplace(*(offset_ptr + 1));
2008 M_insist(
bool(end),
"end must be set");
2012 M_insist(std::in_range<uint64_t>(M.batch_size),
"should fit in uint64_t");
2017 U64x1 num_results = end->clone() - begin->clone();
2018 U64x1 num_results_cpy = num_results.clone();
2019 U64x1 batch_size = M.batch_size == 0 ? num_results.clone() : U64x1(M.batch_size);
2020 U64x1 batch_size_cpy = batch_size.clone();
2021 return Select(batch_size < num_results, batch_size_cpy, num_results_cpy);
2033 auto end_cpy = end->clone();
2034 num_tuples_in_batch =
Select(*end - _begin > alloc_size, alloc_size, end_cpy - _begin);
2040 buffer_address.clone(),
2041 num_tuples_in_batch.val()
2043 _begin += num_tuples_in_batch;
2044 ptr = buffer_address.clone();
2045 WHILE (num_tuples_in_batch > 0
U) {
2046 static Schema empty_schema;
2051 M.scan.store().table().layout(),
2052 M.scan.store().table().schema(M.scan.alias()),
2056 num_tuples_in_batch -= 1U;
2065 IF (alloc_size > U64x1(0)) {
2076 constexpr int64_t value_offset =
2077 ((
sizeof(key_type) +
alignof(value_type) - 1U) /
alignof(value_type)) *
alignof(value_type);
2079 static Schema empty_schema;
2084 M.scan.store().table().layout(),
2085 M.scan.store().table().schema(M.scan.alias()),
2089 lo += int64_t(entry_size);
2103template<
idx::IndexMethod IndexMethod,
typename Index, sql_type SqlT>
2107 index_scan_codegen_compilation<IndexMethod, Index, SqlT>(index, bounds, M, std::move(setup), std::move(pipeline), std::move(teardown));
2109 index_scan_codegen_interpretation<IndexMethod, Index>(index, bounds, M, std::move(setup), std::move(pipeline), std::move(teardown));
2111 index_scan_codegen_hybrid<IndexMethod, Index, SqlT>(index, bounds, M, std::move(setup), std::move(pipeline), std::move(teardown));
2118template<
idx::IndexMethod IndexMethod,
typename AttrT, sql_type SqlT>
2125 auto &table_name = M.scan.store().table().
name();
2127 auto &index_base = DB.get_index(table_name, attribute_name, IndexMethod);
2131 auto &index = as<const idx::ArrayIndex<AttrT>>(index_base);
2132 index_scan_resolve_strategy<IndexMethod, const idx::ArrayIndex<AttrT>, SqlT>(
2133 index, bounds, M, std::move(setup), std::move(pipeline), std::move(teardown)
2136 auto &index = as<const idx::RecursiveModelIndex<AttrT>>(index_base);
2137 index_scan_resolve_strategy<IndexMethod, const idx::RecursiveModelIndex<AttrT>, SqlT>(
2138 index, bounds, M, std::move(setup), std::move(pipeline), std::move(teardown)
2146template<
idx::IndexMethod IndexMethod>
2151 auto &cnf = M.filter.filter();
2155#define RESOLVE_INDEX_METHOD(ATTRTYPE, SQLTYPE) \
2156 index_scan_resolve_index_method<IndexMethod, ATTRTYPE, SQLTYPE>(bounds, M, std::move(setup), std::move(pipeline), std::move(teardown))
2162 case Numeric::N_Int:
2163 case Numeric::N_Decimal:
2172 case Numeric::N_Float:
2185 }, *bounds.attribute.type);
2187#undef RESOLVE_INDEX_METHOD
2190template<
idx::IndexMethod IndexMethod>
2194 auto &schema = M.scan.schema();
2195 M_insist(schema == schema.drop_constants().deduplicate(),
"Schema of `ScanOperator` must neither contain NULL nor duplicates");
2197 auto &table = M.scan.store().table();
2198 M_insist(not table.layout().is_finite(),
"layout for `wasm::IndexScan` must be infinite");
2214template<
bool Predicated>
2229template<
bool Predicated>
2242template<
bool Predicated>
2245 const cnf::CNF &cond = M.filter.filter();
2246 const unsigned cost = std::accumulate(cond.cbegin(), cond.cend(), 0
U, [](
unsigned cost,
const cnf::Clause &clause) {
2247 return cost + clause.size();
2252template<
bool Predicated>
2261 [&, pipeline=std::move(pipeline)](){
2295 const cnf::CNF &cond = M.filter.filter();
2296 M_insist(cond.size() == 1,
"disjunctive filter condition must be a single clause");
2297 return cond[0].size() / 2.0;
2307 [&, pipeline=std::move(pipeline)](){
2309 BLOCK(lazy_disjunctive_filter)
2311 BLOCK(lazy_disjunctive_filter_then)
2315 if (pred.negative())
2316 GOTO(cond.is_false_and_not_null(), lazy_disjunctive_filter_then);
2318 GOTO(cond.is_true_and_not_null(), lazy_disjunctive_filter_then);
2320 GOTO(lazy_disjunctive_filter);
2335 std::size_t child_idx,
2336 const std::tuple<const ProjectionOperator*> &partial_inner_nodes)
2342 auto &projection = *std::get<0>(partial_inner_nodes);
2344 if (not projection.children().empty()) {
2346 auto is_simd_computable = [](
const ast::Expr &e){
2347 bool simd_computable =
true;
2350 if (b.lhs->type()->is_character_sequence() or b.rhs->type()->is_character_sequence()) {
2351 simd_computable =
false;
2354 if (b.common_operand_type->is_integral()
and b.op().type == TK_SLASH) {
2355 simd_computable =
false;
2358 if (b.op().type == TK_PERCENT) {
2359 simd_computable =
false;
2368 return simd_computable;
2370 auto pred = [&](
auto &p){
return not is_simd_computable(p.first); };
2371 if (std::any_of(projection.projections().cbegin(), projection.projections().cend(), pred))
2383 M_insist(M.projection.projections().size() == M.projection.schema().num_entries(),
2384 "projections must match the operator's schema");
2385 std::vector<std::pair<Schema::Identifier, Schema::Identifier>> old2new;
2386 auto p = M.projection.projections().begin();
2387 for (
auto &e: M.projection.schema()) {
2388 auto pred = [&e](
const auto &p) {
return p.second == e.id; };
2389 if (std::find_if(old2new.cbegin(), old2new.cend(), pred) == old2new.cend()) {
2390 M_insist(p != M.projection.projections().end());
2410 auto execute_projection = [&, pipeline=std::move(pipeline)](){
2415 if (old_env.predicated())
2419 M_insist(M.projection.projections().size() == M.projection.schema().num_entries(),
2420 "projections must match the operator's schema");
2421 auto p = M.projection.projections().begin();
2422 for (
auto &e: M.projection.schema()) {
2423 if (not new_env.
has(e.id)
and not e.id.is_constant()) {
2424 if (old_env.has(e.id)) {
2426 new_env.
add(e.id, old_env.get(e.id));
2429 M_insist(p != M.projection.projections().end());
2432 if (
value.can_be_null()) {
2434 new_env.
add(e.id, var);
2444 value.guarantees_terminating_nul()));
2446 [](std::monostate) ->
void {
M_unreachable(
"invalid expression"); },
2447 }, old_env.compile(p->first));
2462 uint64_t min_size_in_bytes = 16;
2463 for (
auto &p : M.projection.projections()) {
2470 if (fn.get_function().is_aggregate())
2472 min_size_in_bytes = std::min(min_size_in_bytes, (fn.type()->size() + 7) / 8);
2473 if (min_size_in_bytes == 1)
2476 [&min_size_in_bytes](
auto &e) ->
void {
2477 min_size_in_bytes = std::min(min_size_in_bytes, (e.type()->size() + 7) / 8);
2478 if (min_size_in_bytes == 1)
2486 M.child->get()->execute(std::move(setup), std::move(execute_projection), std::move(teardown));
2491 execute_projection();
2515 return 1.5 * M.child->get_matched_root().info().estimated_cardinality;
2535 const uint64_t AGGREGATES_SIZE_THRESHOLD_IN_BITS =
2536 M.use_in_place_values ? std::numeric_limits<uint64_t>::max() : 0;
2538 const auto num_keys = M.grouping.group_by().size();
2543 for (std::size_t i = 0; i < num_keys; ++i) {
2544 auto &e = M.grouping.schema()[i];
2545 ht_schema.
add(e.id, e.type, e.constraints);
2549 const auto &aggregates = p.first;
2550 const auto &avg_aggregates = p.second;
2551 uint64_t aggregates_size_in_bits = 0;
2552 for (
auto &info : aggregates) {
2553 ht_schema.
add(info.entry);
2554 aggregates_size_in_bits += info.entry.type->size();
2561 std::unique_ptr<HashTable> ht;
2562 std::vector<HashTable::index_t> key_indices(num_keys);
2563 std::iota(key_indices.begin(), key_indices.end(), 0);
2564 if (M.use_open_addressing_hashing) {
2565 if (aggregates_size_in_bits < AGGREGATES_SIZE_THRESHOLD_IN_BITS)
2566 ht = std::make_unique<GlobalOpenAddressingInPlaceHashTable>(ht_schema, std::move(key_indices),
2569 ht = std::make_unique<GlobalOpenAddressingOutOfPlaceHashTable>(ht_schema, std::move(key_indices),
2571 if (M.use_quadratic_probing)
2572 as<OpenAddressingHashTableBase>(*ht).set_probing_strategy<
QuadraticProbing>();
2574 as<OpenAddressingHashTableBase>(*ht).set_probing_strategy<
LinearProbing>();
2576 ht = std::make_unique<GlobalChainedHashTable>(ht_schema, std::move(key_indices), initial_capacity);
2580 FUNCTION(hash_based_grouping_child_pipeline,
void(
void))
2584 std::optional<HashTable::entry_t> dummy;
2589 ht->set_high_watermark(M.load_factor);
2590 dummy.emplace(ht->dummy_entry());
2597 std::vector<SQL_t> key;
2598 for (
auto &p : M.grouping.group_by())
2599 key.emplace_back(env.compile(p.first.get()));
2600 auto [entry, inserted] = ht->try_emplace(std::move(key));
2603 Block init_aggs(
"hash_based_grouping.init_aggs",
false),
2604 update_aggs(
"hash_based_grouping.update_aggs",
false),
2605 update_avg_aggs(
"hash_based_grouping.update_avg_aggs",
false);
2606 for (
auto &info : aggregates) {
2607 bool is_min =
false;
2608 switch (info.fnid) {
2611 case m::Function::FN_MIN:
2613 case m::Function::FN_MAX: {
2615 "MIN and MAX aggregate functions expect exactly one argument");
2616 const auto &arg = *info.args[0];
2619 requires (not (std::same_as<_T, _Boolx1> or std::same_as<_T, NChar>)) {
2620 using type =
typename _T::type;
2623 auto _arg = env.compile(arg);
2624 _T _new_val = convert<_T>(_arg);
2627 auto [val_,
is_null] = _new_val.clone().split();
2630 auto neutral = is_min ?
T(std::numeric_limits<type>::max())
2631 :
T(std::numeric_limits<type>::lowest());
2632 r.clone().set_value(neutral);
2633 if (info.entry.nullable())
2634 r.clone().set_null();
2636 r.clone().set_value(val);
2637 if (info.entry.nullable())
2638 r.clone().set_not_null();
2642 if (_new_val.can_be_null()) {
2644 auto [new_val_, new_val_is_null_] = _new_val.split();
2645 auto [old_min_max_, old_min_max_is_null] = _T(r.clone()).split();
2646 const Var<Boolx1> new_val_is_null(new_val_is_null_);
2648 auto chosen_r =
Select(new_val_is_null, dummy->extract<_T>(info.entry.id),
2650 if constexpr (std::floating_point<type>) {
2652 is_min ?
min(old_min_max_, new_val_)
2653 :
max(old_min_max_, new_val_)
2656 const Var<T> new_val(new_val_),
2657 old_min_max(old_min_max_);
2658 auto cmp = is_min ? new_val < old_min_max : new_val > old_min_max;
2667 r.set_value(new_val);
2672 old_min_max_is_null
and new_val_is_null
2675 auto new_val_ = _new_val.insist_not_null();
2676 auto old_min_max_ = _T(r.clone()).insist_not_null();
2677 if constexpr (std::floating_point<type>) {
2679 is_min ?
min(old_min_max_, new_val_)
2680 :
max(old_min_max_, new_val_)
2683 const Var<T> new_val(new_val_),
2684 old_min_max(old_min_max_);
2685 auto cmp = is_min ? new_val < old_min_max : new_val > old_min_max;
2694 r.set_value(new_val);
2703 requires std::same_as<_T,_Boolx1> or std::same_as<_T, NChar> {
2706 [](std::monostate) ->
void {
M_unreachable(
"invalid reference"); },
2707 }, entry.extract(info.entry.id));
2710 case m::Function::FN_AVG: {
2711 auto it = avg_aggregates.find(info.entry.id);
2712 M_insist(it != avg_aggregates.end());
2713 const auto &avg_info = it->second;
2714 M_insist(avg_info.compute_running_avg,
2715 "AVG aggregate may only occur for running average computations");
2716 M_insist(info.args.size() == 1,
"AVG aggregate function expects exactly one argument");
2717 const auto &arg = *info.args[0];
2719 auto r = entry.extract<_Doublex1>(info.entry.id);
2720 auto _arg = env.compile(arg);
2721 _Doublex1 _new_val = convert<_Doublex1>(_arg);
2724 auto [val_,
is_null] = _new_val.clone().split();
2727 r.clone().set_value(Doublex1(0.0));
2728 if (info.entry.nullable())
2729 r.clone().set_null();
2731 r.clone().set_value(val);
2732 if (info.entry.nullable())
2733 r.clone().set_not_null();
2739 if (_new_val.can_be_null()) {
2741 auto [new_val, new_val_is_null_] = _new_val.split();
2742 auto [old_avg_, old_avg_is_null] = _Doublex1(r.clone()).split();
2743 const Var<Boolx1> new_val_is_null(new_val_is_null_);
2746 auto delta_absolute = new_val - old_avg;
2747 auto running_count = _I64x1(entry.get<_I64x1>(avg_info.running_count)).insist_not_null();
2748 auto delta_relative = delta_absolute / running_count.to<
double>();
2750 auto chosen_r =
Select(new_val_is_null, dummy->extract<_Doublex1>(info.entry.id),
2753 old_avg + delta_relative
2756 old_avg_is_null
and new_val_is_null
2759 auto new_val = _new_val.insist_not_null();
2760 auto old_avg_ = _Doublex1(r.clone()).insist_not_null();
2763 auto delta_absolute = new_val - old_avg;
2764 auto running_count = _I64x1(entry.get<_I64x1>(avg_info.running_count)).insist_not_null();
2765 auto delta_relative = delta_absolute / running_count.to<
double>();
2767 old_avg + delta_relative
2774 case m::Function::FN_SUM: {
2775 M_insist(info.args.size() == 1,
"SUM aggregate function expects exactly one argument");
2776 const auto &arg = *info.args[0];
2779 requires (not (std::same_as<_T, _Boolx1> or std::same_as<_T, NChar>)) {
2780 using type =
typename _T::type;
2783 auto _arg = env.compile(arg);
2784 _T _new_val = convert<_T>(_arg);
2787 auto [val_,
is_null] = _new_val.clone().split();
2790 r.clone().set_value(
T(type(0)));
2791 if (info.entry.nullable())
2792 r.clone().set_null();
2794 r.clone().set_value(val);
2795 if (info.entry.nullable())
2796 r.clone().set_not_null();
2800 if (_new_val.can_be_null()) {
2802 auto [new_val, new_val_is_null_] = _new_val.split();
2803 auto [old_sum, old_sum_is_null] = _T(r.clone()).split();
2804 const Var<Boolx1> new_val_is_null(new_val_is_null_);
2806 auto chosen_r =
Select(new_val_is_null, dummy->extract<_T>(info.entry.id),
2812 old_sum_is_null
and new_val_is_null
2815 auto new_val = _new_val.insist_not_null();
2816 auto old_sum = _T(r.clone()).insist_not_null();
2825 requires std::same_as<_T,_Boolx1> or std::same_as<_T, NChar> {
2828 [](std::monostate) ->
void {
M_unreachable(
"invalid reference"); },
2829 }, entry.extract(info.entry.id));
2832 case m::Function::FN_COUNT: {
2833 M_insist(info.args.size() <= 1,
"COUNT aggregate function expects at most one argument");
2835 auto r = entry.get<_I64x1>(info.entry.id);
2837 if (info.args.empty()) {
2839 r.clone() = _I64x1(1);
2842 auto old_count = _I64x1(r.clone()).insist_not_null();
2844 old_count + int64_t(1)
2849 const auto &arg = *info.args[0];
2851 auto _arg = env.compile(arg);
2852 I64x1 new_val_not_null =
not_null(_arg).to<int64_t>();
2855 r.clone() = _I64x1(new_val_not_null.clone());
2858 auto old_count = _I64x1(r.clone()).insist_not_null();
2860 old_count + new_val_not_null
2872 init_aggs.attach_to_current();
2874 update_aggs.attach_to_current();
2881 hash_based_grouping_child_pipeline();
2886 setup_t(std::move(setup), [&](){ ht->setup(); })();
2890 for (std::size_t i = 0; i < num_keys; ++i) {
2891 auto &e = M.grouping.schema()[i];
2892 key_schema.
add(e.id, e.type, e.constraints);
2896 for (
auto &e : M.grouping.schema().deduplicate()) {
2898 key_schema.find(e.id);
2903 if (
auto it = avg_aggregates.find(e.id);
2904 it != avg_aggregates.end()
and not it->second.compute_running_avg)
2906 auto &avg_info = it->second;
2907 auto sum = std::visit(overloaded {
2908 [&]<sql_type T>(HashTable::const_reference_t<T> &&r) -> _Doublex1
2909 requires (std::same_as<T, _I64x1> or std::same_as<T, _Doublex1>) {
2910 return T(r).template to<double>();
2912 [](auto&&) -> _Doublex1 { M_unreachable(
"invalid type"); },
2913 [](std::monostate&&) -> _Doublex1 { M_unreachable(
"invalid reference"); },
2914 }, entry.
get(avg_info.sum));
2915 auto count = _I64x1(entry.
get<_I64x1>(avg_info.running_count)).insist_not_null().to<
double>();
2916 auto avg = sum / count;
2917 if (avg.can_be_null()) {
2923 env.add(e.id, _Doublex1(var));
2929 if (
value.can_be_null()) {
2942 value.guarantees_terminating_nul()));
2944 [](std::monostate&&) ->
void {
M_unreachable(
"invalid reference"); },
2945 }, entry.
get(e.id));
2952 teardown_t(std::move(teardown), [&](){ ht->teardown(); })();
2956 std::size_t child_idx,
2957 const std::tuple<const GroupingOperator*> &partial_inner_nodes)
2965 for (
auto &p : std::get<0>(partial_inner_nodes)->group_by()) {
2967 if (orders.
find(
id) == orders.
cend())
2980 return 1.0 * M.child->get_matched_root().info().estimated_cardinality;
2993 for (
auto &[
expr, alias] : M.grouping.group_by()) {
2995 M_insist(it != sortedness_child.orders().cend());
2998 if (orders.
find(
id) == orders.
cend())
2999 orders.
add(std::move(
id), it->second);
3012 const auto num_keys = M.grouping.group_by().size();
3016 for (std::size_t i = 0; i < num_keys; ++i) {
3017 auto &e = M.grouping.schema()[i];
3018 key_schema.
add(e.id, e.type, e.constraints);
3023 const auto &aggregates = p.first;
3024 const auto &avg_aggregates = p.second;
3027 FunctionProxy<void(
void)> emit_group_and_resume_pipeline(
"emit_group_and_resume_pipeline");
3029 std::optional<Var<Boolx1>> first_iteration;
3035 agg_t agg_values[aggregates.size()];
3036 agg_backup_t agg_value_backups[aggregates.size()];
3040 key_t key_values[num_keys];
3041 key_backup_t key_value_backups[num_keys];
3043 auto store_locals_to_globals = [&](){
3045 for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
3046 auto &info = aggregates[idx];
3048 switch (info.fnid) {
3051 case m::Function::FN_MIN:
3052 case m::Function::FN_MAX: {
3053 auto min_max = [&]<
typename T>() {
3057 auto &[min_max_backup, is_null_backup] = *
M_notnull((
3063 min_max_backup = min_max;
3067 auto &
n = as<const Numeric>(*info.entry.type);
3069 case Numeric::N_Int:
3070 case Numeric::N_Decimal:
3073 case 8: min_max.template operator()<int8_t >();
break;
3074 case 16: min_max.template operator()<int16_t>();
break;
3075 case 32: min_max.template operator()<int32_t>();
break;
3076 case 64: min_max.template operator()<int64_t>();
break;
3079 case Numeric::N_Float:
3081 min_max.template
operator()<
float>();
3083 min_max.template operator()<
double>();
3087 case m::Function::FN_AVG: {
3091 auto &[avg_backup, is_null_backup] = *
M_notnull((
3102 case m::Function::FN_SUM: {
3103 M_insist(info.args.size() == 1,
"SUM aggregate function expects exactly one argument");
3104 const auto &arg = *info.args[0];
3106 auto sum = [&]<
typename T>() {
3110 auto &[sum_backup, is_null_backup] = *
M_notnull((
3120 auto &
n = as<const Numeric>(*info.entry.type);
3122 case Numeric::N_Int:
3123 case Numeric::N_Decimal:
3126 case 8: sum.template operator()<int8_t >();
break;
3127 case 16: sum.template operator()<int16_t>();
break;
3128 case 32: sum.template operator()<int32_t>();
break;
3129 case 64: sum.template operator()<int64_t>();
break;
3132 case Numeric::N_Float:
3134 sum.template
operator()<
float>();
3136 sum.template operator()<
double>();
3140 case m::Function::FN_COUNT: {
3144 count_backup = count;
3152 auto store = [&]<
typename T>(std::size_t idx) {
3156 auto &[key_backup, is_null_backup] = *
M_notnull((
3165 for (std::size_t idx = 0; idx < num_keys; ++idx) {
3167 [&](
const Boolean&) { store.template operator()<
bool>(idx); },
3170 case Numeric::N_Int:
3171 case Numeric::N_Decimal:
3174 case 8: store.template operator()<int8_t >(idx);
break;
3175 case 16: store.template operator()<int16_t>(idx);
break;
3176 case 32: store.template operator()<int32_t>(idx);
break;
3177 case 64: store.template operator()<int64_t>(idx);
break;
3180 case Numeric::N_Float:
3182 store.template
operator()<
float>(idx);
3184 store.template operator()<
double>(idx);
3193 [&](
const Date&) { store.template operator()<int32_t>(idx); },
3194 [&](
const DateTime&) { store.template operator()<int64_t>(idx); },
3196 }, *M.grouping.schema()[idx].type);
3202 first_iteration.emplace(first_iteration_backup);
3205 for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
3206 auto &info = aggregates[idx];
3207 const bool nullable = info.entry.nullable();
3209 bool is_min =
false;
3210 switch (info.fnid) {
3212 M_unreachable(
"unsupported aggregate function");
3213 case m::Function::FN_MIN:
3215 case m::Function::FN_MAX: {
3216 auto min_max = [&]<typename T>() {
3217 auto neutral = is_min ? std::numeric_limits<T>::max()
3218 : std::numeric_limits<T>::lowest();
3220 Var<PrimitiveExpr<T>> min_max;
3221 Global<PrimitiveExpr<T>> min_max_backup(neutral);
3222 std::optional<Var<Boolx1>> is_null;
3223 std::optional<Global<Boolx1>> is_null_backup;
3226 min_max = min_max_backup;
3228 is_null_backup.emplace(true);
3229 is_null.emplace(*is_null_backup);
3234 results.add(info.entry.id, Select(*is_null_backup, Expr<T>::Null(), min_max_backup));
3236 results.add(info.entry.id, min_max_backup.val());
3239 new (&agg_values[idx]) agg_t(std::make_pair(std::move(min_max), std::move(is_null)));
3240 new (&agg_value_backups[idx]) agg_backup_t(std::make_pair(
3241 std::move(min_max_backup), std::move(is_null_backup)
3244 auto &n = as<const Numeric>(*info.entry.type);
3246 case Numeric::N_Int:
3247 case Numeric::N_Decimal:
3249 default: M_unreachable(
"invalid size");
3250 case 8: min_max.template operator()<int8_t >(); break;
3251 case 16: min_max.template operator()<int16_t>(); break;
3252 case 32: min_max.template operator()<int32_t>(); break;
3253 case 64: min_max.template operator()<int64_t>(); break;
3256 case Numeric::N_Float:
3258 min_max.template operator()<float>();
3260 min_max.template operator()<double>();
3264 case m::Function::FN_AVG: {
3266 Global<Doublex1> avg_backup(0.0);
3267 std::optional<Var<Boolx1>> is_null;
3268 std::optional<Global<Boolx1>> is_null_backup;
3273 is_null_backup.emplace(true);
3274 is_null.emplace(*is_null_backup);
3279 results.
add(info.entry.id,
Select(*is_null_backup, _Doublex1::Null(), avg_backup));
3281 results.
add(info.entry.id, avg_backup.val());
3284 new (&agg_values[idx]) agg_t(std::make_pair(std::move(avg), std::move(
is_null)));
3285 new (&agg_value_backups[idx]) agg_backup_t(std::make_pair(
3286 std::move(avg_backup), std::move(is_null_backup)
3291 case m::Function::FN_SUM: {
3292 auto sum = [&]<
typename T>() {
3295 std::optional<Var<Boolx1>>
is_null;
3296 std::optional<Global<Boolx1>> is_null_backup;
3301 is_null_backup.emplace(
true);
3302 is_null.emplace(*is_null_backup);
3309 results.
add(info.entry.id, sum_backup.val());
3312 new (&agg_values[idx]) agg_t(std::make_pair(std::move(sum), std::move(
is_null)));
3313 new (&agg_value_backups[idx]) agg_backup_t(std::make_pair(
3314 std::move(sum_backup), std::move(is_null_backup)
3317 auto &
n = as<const Numeric>(*info.entry.type);
3319 case Numeric::N_Int:
3320 case Numeric::N_Decimal:
3323 case 8: sum.template operator()<int8_t >();
break;
3324 case 16: sum.template operator()<int16_t>();
break;
3325 case 32: sum.template operator()<int32_t>();
break;
3326 case 64: sum.template operator()<int64_t>();
break;
3329 case Numeric::N_Float:
3331 sum.template
operator()<
float>();
3333 sum.template operator()<
double>();
3337 case m::Function::FN_COUNT: {
3343 count = count_backup;
3346 results.
add(info.entry.id, count_backup.val());
3349 new (&agg_values[idx]) agg_t(std::move(count));
3350 new (&agg_value_backups[idx]) agg_backup_t(std::move(count_backup));
3358 auto init = [&]<
typename T>(std::size_t idx) {
3359 const bool nullable = M.grouping.schema()[idx].nullable();
3363 std::optional<Var<Boolx1>>
is_null;
3364 std::optional<Global<Boolx1>> is_null_backup;
3369 is_null_backup.emplace();
3370 is_null.emplace(*is_null_backup);
3374 auto id = M.grouping.schema()[idx].id;
3375 key_schema.find(
id);
3381 results.add(
id, key_backup.val());
3387 new (&key_values[idx]) key_t(std::make_pair(std::move(key), std::move(
is_null)));
3388 new (&key_value_backups[idx]) key_backup_t(std::make_pair(
3389 std::move(key_backup), std::move(is_null_backup)
3392 for (std::size_t idx = 0; idx < num_keys; ++idx) {
3394 [&](
const Boolean&) {
init.template operator()<
bool>(idx); },
3397 case Numeric::N_Int:
3398 case Numeric::N_Decimal:
3401 case 8:
init.template operator()<int8_t >(idx);
break;
3402 case 16:
init.template operator()<int16_t>(idx);
break;
3403 case 32:
init.template operator()<int32_t>(idx);
break;
3404 case 64:
init.template operator()<int64_t>(idx);
break;
3407 case Numeric::N_Float:
3409 init.template
operator()<
float>(idx);
3411 init.template operator()<
double>(idx);
3423 auto id = M.grouping.schema()[idx].id;
3424 key_schema.find(
id);
3427 NChar str(key_backup.val(), M.grouping.schema()[idx].nullable(), cs.length, cs.is_varying);
3428 results.add(
id, std::move(str));
3435 new (&key_values[idx]) key_t(std::move(key));
3436 new (&key_value_backups[idx]) key_backup_t(std::move(key_backup));
3438 [&](
const Date&) {
init.template operator()<int32_t>(idx); },
3439 [&](
const DateTime&) {
init.template operator()<int64_t>(idx); },
3441 }, *M.grouping.schema()[idx].type);
3448 std::optional<Var<Boolx1>> pred;
3449 if (env.predicated()) {
3451 pred = env.extract_predicate<_Boolx1>().is_true_and_not_null();
3455 Block reset_aggs(
"ordered_grouping.reset_aggs",
false),
3456 update_aggs(
"ordered_grouping.update_aggs",
false),
3457 update_avg_aggs(
"ordered_grouping.update_avg_aggs",
false);
3458 for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
3459 auto &info = aggregates[idx];
3461 bool is_min =
false;
3462 switch (info.fnid) {
3465 case m::Function::FN_MIN:
3467 case m::Function::FN_MAX: {
3468 M_insist(info.args.size() == 1,
"MIN and MAX aggregate functions expect exactly one argument");
3469 const auto &arg = *info.args[0];
3470 auto min_max = [&]<
typename T>() {
3471 auto neutral = is_min ? std::numeric_limits<T>::max()
3472 :
std::numeric_limits<
T>::lowest();
3485 auto _arg = env.compile(arg);
3486 Expr<T> _new_val = convert<Expr<T>>(_arg);
3491 auto [new_val_, new_val_is_null_] = _new_val_pred.split();
3492 const Var<Boolx1> new_val_is_null(new_val_is_null_);
3494 if constexpr (std::floating_point<T>) {
3495 min_max =
Select(new_val_is_null,
3497 is_min ?
min(min_max, new_val_)
3498 :
max(min_max, new_val_));
3501 auto cmp = is_min ? new_val < min_max : new_val > min_max;
3503 min_max =
Select(new_val_is_null,
3509 IF (not new_val_is_null
and cmp) {
3516 auto _new_val_pred = pred ?
Select(*pred, _new_val, neutral) : _new_val;
3517 auto new_val_ = _new_val_pred.insist_not_null();
3518 if constexpr (std::floating_point<T>) {
3519 min_max = is_min ?
min(min_max, new_val_)
3520 :
max(min_max, new_val_);
3523 auto cmp = is_min ? new_val < min_max : new_val > min_max;
3537 auto &
n = as<const Numeric>(*info.entry.type);
3539 case Numeric::N_Int:
3540 case Numeric::N_Decimal:
3543 case 8: min_max.template operator()<int8_t >();
break;
3544 case 16: min_max.template operator()<int16_t>();
break;
3545 case 32: min_max.template operator()<int32_t>();
break;
3546 case 64: min_max.template operator()<int64_t>();
break;
3549 case Numeric::N_Float:
3551 min_max.template
operator()<
float>();
3553 min_max.template operator()<
double>();
3557 case m::Function::FN_AVG:
3559 case m::Function::FN_SUM: {
3560 M_insist(info.args.size() == 1,
"SUM aggregate function expects exactly one argument");
3561 const auto &arg = *info.args[0];
3563 auto sum = [&]<
typename T>() {
3575 auto _arg = env.compile(arg);
3576 Expr<T> _new_val = convert<Expr<T>>(_arg);
3581 auto [new_val, new_val_is_null_] = _new_val_pred.split();
3582 const Var<Boolx1> new_val_is_null(new_val_is_null_);
3584 sum +=
Select(new_val_is_null,
3589 auto _new_val_pred = pred ?
Select(*pred, _new_val,
T(0)) : _new_val;
3590 sum += _new_val_pred.insist_not_null();
3594 auto &
n = as<const Numeric>(*info.entry.type);
3596 case Numeric::N_Int:
3597 case Numeric::N_Decimal:
3600 case 8: sum.template operator()<int8_t >();
break;
3601 case 16: sum.template operator()<int16_t>();
break;
3602 case 32: sum.template operator()<int32_t>();
break;
3603 case 64: sum.template operator()<int64_t>();
break;
3606 case Numeric::N_Float:
3608 sum.template
operator()<
float>();
3610 sum.template operator()<
double>();
3614 case m::Function::FN_COUNT: {
3615 M_insist(info.args.size() <= 1,
"COUNT aggregate function expects at most one argument");
3616 M_insist(info.entry.type->is_integral()
and info.entry.type->size() == 64);
3625 if (info.args.empty()) {
3626 count += pred ? pred->to<int64_t>() : I64x1(1);
3628 auto _new_val = env.compile(*info.args[0]);
3631 I64x1 inc = pred ? (
not_null(_new_val)
and *pred).to<int64_t>()
3636 I64x1 inc = pred ? pred->to<int64_t>() : I64x1(1);
3647 for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
3648 auto &info = aggregates[idx];
3650 if (info.fnid == m::Function::FN_AVG) {
3651 M_insist(info.args.size() == 1,
"AVG aggregate function expects exactly one argument");
3652 const auto &arg = *info.args[0];
3653 M_insist(info.entry.type->is_double());
3655 auto it = avg_aggregates.find(info.entry.id);
3656 M_insist(it != avg_aggregates.end());
3657 const auto &avg_info = it->second;
3658 M_insist(avg_info.compute_running_avg,
3659 "AVG aggregate may only occur for running average computations");
3674 auto running_count_idx = std::distance(
3675 aggregates.cbegin(),
3676 std::find_if(aggregates.cbegin(), aggregates.cend(), [&avg_info](
const auto &info){
3677 return info.entry.id == avg_info.running_count;
3680 M_insist(0 <= running_count_idx
and running_count_idx < aggregates.size());
3681 auto &running_count = *
M_notnull(std::get_if<
Var<I64x1>>(&agg_values[running_count_idx]));
3683 auto _arg = env.compile(arg);
3684 _Doublex1 _new_val = convert<_Doublex1>(_arg);
3686 if (_new_val.can_be_null()) {
3688 auto _new_val_pred = pred ?
Select(*pred, _new_val, _Doublex1::Null()) : _new_val;
3689 auto [new_val, new_val_is_null_] = _new_val_pred.split();
3690 const Var<Boolx1> new_val_is_null(new_val_is_null_);
3692 auto delta_absolute = new_val - avg;
3693 auto delta_relative = delta_absolute / running_count.to<
double>();
3695 avg +=
Select(new_val_is_null,
3700 auto _new_val_pred = pred ?
Select(*pred, _new_val, avg) : _new_val;
3701 auto delta_absolute = _new_val_pred.insist_not_null() - avg;
3702 auto delta_relative = delta_absolute / running_count.to<
double>();
3704 avg += delta_relative;
3711 std::optional<Boolx1> group_differs;
3712 Block update_keys(
"ordered_grouping.update_grouping_keys",
false);
3713 for (std::size_t idx = 0; idx < num_keys; ++idx) {
3716 auto &[key_val, key_is_null] = *
M_notnull((
3721 if (
value.can_be_null()) {
3724 auto null_differs =
is_null != *key_is_null;
3725 Boolx1 key_differs = null_differs or (not *key_is_null
and val != key_val);
3727 group_differs.emplace(key_differs or *group_differs);
3729 group_differs.emplace(key_differs);
3732 std::tie(key_val, key_is_null) =
value.split();
3735 Boolx1 key_differs = key_val !=
value.clone().insist_not_null();
3737 group_differs.emplace(key_differs or *group_differs);
3739 group_differs.emplace(key_differs);
3742 key_val =
value.insist_not_null();
3749 auto [key_addr, key_is_nullptr] = key.val().split();
3750 auto [addr, is_nullptr] =
value.val().clone().split();
3753 value.guarantees_terminating_nul()),
3755 value.guarantees_terminating_nul()),
3756 U32x1(
value.length()),
3759 auto [addr_differs_value, addr_differs_is_null] = addr_differs.split();
3760 addr_differs_is_null.discard();
3761 auto nullptr_differs = is_nullptr != key_is_nullptr.clone();
3762 Boolx1 key_differs = nullptr_differs or (not key_is_nullptr
and addr_differs_value);
3764 group_differs.emplace(key_differs or *group_differs);
3766 group_differs.emplace(key_differs);
3772 [](
auto) ->
void {
M_unreachable(
"SIMDfication currently not supported"); },
3773 [](std::monostate) ->
void {
M_unreachable(
"invalid expression"); },
3774 }, env.compile(M.grouping.group_by()[idx].first.get()));
3780 Boolx1 cond = *first_iteration or *group_differs;
3781 IF (pred ?
Select(*pred, cond,
false) : cond) {
3782 IF (not *first_iteration) {
3783 store_locals_to_globals();
3784 emit_group_and_resume_pipeline();
3785 reset_aggs.attach_to_current();
3787 update_keys.attach_to_current();
3788 *first_iteration =
false;
3792 update_aggs.attach_to_current();
3793 update_avg_aggs.attach_to_current();
3796 store_locals_to_globals();
3799 for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
3800 agg_values[idx].~agg_t();
3801 agg_value_backups[idx].~agg_backup_t();
3805 first_iteration_backup = *first_iteration;
3806 first_iteration.reset();
3811 IF (not first_iteration_backup) {
3812 emit_group_and_resume_pipeline();
3816 auto fn = emit_group_and_resume_pipeline.make_function();
3825 for (
auto &e : M.grouping.schema().deduplicate()) {
3827 key_schema.find(e.id);
3832 if (
auto it = avg_aggregates.find(e.id);
3833 it != avg_aggregates.end()
and not it->second.compute_running_avg)
3835 auto &avg_info = it->second;
3836 auto sum = results.get(avg_info.sum);
3837 auto count = results.get<_I64x1>(avg_info.running_count).insist_not_null().to<
double>();
3838 auto avg = convert<_Doublex1>(sum) / count;
3839 if (avg.can_be_null()) {
3845 env.add(e.id, _Doublex1(var));
3850 if (
value.can_be_null()) {
3862 value.guarantees_terminating_nul()));
3864 [](
auto) ->
void {
M_unreachable(
"SIMDfication currently not supported"); },
3865 [](std::monostate) ->
void {
M_unreachable(
"invalid reference"); },
3866 }, results.get(e.id));
3901 for (
auto &e : M.aggregation.schema().deduplicate())
3915 std::vector<std::function<void(
void)>> finalize_aggregates;
3919 const auto &aggregates = p.first;
3920 const auto &avg_aggregates = p.second;
3923 uint64_t min_size_in_bytes = 16;
3924 for (
auto &fn : M.aggregation.aggregates()) {
3925 for (
auto &e : fn.get().args) {
3932 M_insist(not fn.get_function().is_aggregate(),
"aggregate arguments must not be aggregates");
3933 min_size_in_bytes = std::min(min_size_in_bytes, (fn.type()->size() + 7) / 8);
3934 if (min_size_in_bytes == 1)
3937 [&min_size_in_bytes](
auto &e) ->
void {
3938 min_size_in_bytes = std::min(min_size_in_bytes, (e.type()->size() + 7) / 8);
3939 if (min_size_in_bytes == 1)
3948 if (std::any_of(avg_aggregates.begin(), avg_aggregates.end(), [](
auto &i){ return i.second.compute_running_avg; }))
3952 FUNCTION(aggregation_child_pipeline,
void(
void))
3960 void *_agg_value_backups;
3964 auto execute_setup = [&]<std::size_t
L>() {
3972 auto agg_values =
new agg_t[aggregates.size()];
3973 auto agg_value_backups =
new agg_backup_t[aggregates.size()];
3976 _agg_values =
static_cast<void*
>(agg_values);
3977 _agg_value_backups =
static_cast<void*
>(agg_value_backups);
3980 for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
3981 auto &info = aggregates[idx];
3983 bool is_min =
false;
3984 switch (info.fnid) {
3987 case m::Function::FN_MIN:
3989 case m::Function::FN_MAX: {
3990 auto min_max = [&]<
typename T>() {
3991 auto neutral = is_min ? std::numeric_limits<T>::max()
3992 : std::numeric_limits<T>::lowest();
4002 min_max = min_max_backup;
4006 if constexpr (
L == 1) {
4008 Boolx1
is_null = is_null_backup;
4020 auto simd_is_null =
new Bool<L>(is_null_backup.val());
4021 finalize_aggregates.emplace_back([&, is_min, simd_min_max, simd_is_null]() {
4024 auto update = [&]<std::size_t I>(){
4026 res = is_min ?
min(
res, simd_min_max->clone().template extract<I>())
4027 :
max(
res, simd_min_max->clone().template extract<I>());
4030 simd_min_max->clone().template extract<I>()
4032 auto cmp = is_min ? extracted < res : extracted >
res;
4042 (update.template operator()<Is + 1>(), ...);
4044 }(std::make_index_sequence<
L - 1>{});
4045 simd_min_max->discard();
4046 Boolx1
is_null = simd_is_null->all_true();
4048 delete simd_min_max;
4049 delete simd_is_null;
4054 new (&agg_values[idx]) agg_t(std::make_pair(
4055 std::move(min_max), std::move(
is_null))
4057 new (&agg_value_backups[idx]) agg_backup_t(std::make_pair(
4058 std::move(min_max_backup), std::move(is_null_backup)
4061 auto &
n = as<const Numeric>(*info.entry.type);
4063 case Numeric::N_Int:
4064 case Numeric::N_Decimal:
4067 case 8: min_max.template operator()<int8_t >();
break;
4068 case 16: min_max.template operator()<int16_t>();
break;
4069 case 32: min_max.template operator()<int32_t>();
break;
4070 case 64: min_max.template operator()<int64_t>();
break;
4073 case Numeric::N_Float:
4075 min_max.template
operator()<
float>();
4077 min_max.template operator()<
double>();
4081 case m::Function::FN_AVG:
4083 case m::Function::FN_SUM: {
4084 auto sum = [&]<
typename T>() {
4095 if constexpr (
L == 1) {
4097 Boolx1
is_null = is_null_backup;
4101 return (sum_backup.template extract<Is>() + ...);
4102 }(std::make_index_sequence<L>{});
4103 Boolx1
is_null = is_null_backup.all_true();
4108 new (&agg_values[idx]) agg_t(std::make_pair(std::move(sum), std::move(
is_null)));
4109 new (&agg_value_backups[idx]) agg_backup_t(std::make_pair(
4110 std::move(sum_backup), std::move(is_null_backup)
4113 auto &
n = as<const Numeric>(*info.entry.type);
4115 case Numeric::N_Int:
4116 case Numeric::N_Decimal:
4119 case 8: sum.template operator()<int8_t >();
break;
4120 case 16: sum.template operator()<int16_t>();
break;
4121 case 32: sum.template operator()<int32_t>();
break;
4122 case 64: sum.template operator()<int64_t>();
break;
4125 case Numeric::N_Float:
4127 sum.template
operator()<
float>();
4129 sum.template operator()<
double>();
4133 case m::Function::FN_COUNT: {
4139 count = count_backup;
4142 if constexpr (
L == 1) {
4143 I64x1
value = count_backup;
4146 I64x1
value = [&]<std::size_t... Is>(std::index_sequence<Is...>) {
4147 return (count_backup.template extract<Is>() + ...);
4148 }(std::make_index_sequence<L>{});
4153 new (&agg_values[idx]) agg_t(std::move(count));
4154 new (&agg_value_backups[idx]) agg_backup_t(std::move(count_backup));
4162 for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
4163 auto &info = aggregates[idx];
4165 if (info.fnid == m::Function::FN_AVG) {
4176 if constexpr (
L == 1) {
4177 Doublex1
value = avg_backup;
4178 Boolx1
is_null = is_null_backup;
4189 auto simd_avg =
new Double<L>(avg_backup.val());
4190 auto simd_is_null =
new Bool<L>(is_null_backup.val());
4191 auto simd_running_count =
new I64<L>([&](){
4192 auto it = avg_aggregates.find(info.entry.id);
4193 M_insist(it != avg_aggregates.end());
4194 const auto &avg_info = it->second;
4195 M_insist(avg_info.compute_running_avg,
4196 "AVG aggregate may only occur for running average computations");
4198 auto running_count_idx = std::distance(
4199 aggregates.cbegin(),
4201 aggregates.cbegin(), aggregates.cend(), [&avg_info](
const auto &info){
4202 return info.entry.id == avg_info.running_count;
4205 M_insist(0 <= running_count_idx
and running_count_idx < aggregates.size());
4207 auto &running_count =
4208 *
M_notnull(std::get_if<
Global<I64<L>>>(&agg_value_backups[running_count_idx]));
4209 return running_count.val();
4211 finalize_aggregates.emplace_back([&, simd_avg, simd_is_null, simd_running_count]() {
4212 Doublex1
value = [&]<std::size_t... Is>(std::index_sequence<Is...>) {
4213 I64x1 count = (simd_running_count->clone().template extract<Is>() + ...);
4215 if constexpr (
L != 2) {
4216 return *simd_avg * simd_running_count->template to<double>();
4218 M_unreachable(
"conversion from `I64<2>` to `Double<2>` not supported");
4219 return Double<L>(0.0);
4222 return (simd_sum.template extract<Is>() + ...) / count.to<
double>();
4223 }(std::make_index_sequence<L>{});
4224 Boolx1
is_null = simd_is_null->all_true();
4227 delete simd_is_null;
4228 delete simd_running_count;
4233 new (&agg_values[idx]) agg_t(std::make_pair(std::move(avg), std::move(
is_null)));
4234 new (&agg_value_backups[idx]) agg_backup_t(std::make_pair(
4235 std::move(avg_backup), std::move(is_null_backup)
4242 case 1: execute_setup.operator()<1>();
break;
4243 case 2: execute_setup.operator()<2>();
break;
4244 case 4: execute_setup.operator()<4>();
break;
4245 case 8: execute_setup.operator()<8>();
break;
4246 case 16: execute_setup.operator()<16>();
break;
4247 case 32: execute_setup.operator()<32>();
break;
4251 auto execute_pipeline = [&]<std::size_t
L>(){
4254 "number of SIMD lanes in pipeline callback must match the one in setup callback");
4260 auto agg_values =
static_cast<agg_t*
>(_agg_values);
4261 auto agg_value_backups =
static_cast<agg_backup_t*
>(_agg_value_backups);
4266 std::optional<Var<Bool<L>>> pred;
4267 if (env.predicated()) {
4275 for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
4276 auto &info = aggregates[idx];
4278 bool is_min =
false;
4279 switch (info.fnid) {
4282 case m::Function::FN_MIN:
4284 case m::Function::FN_MAX: {
4286 "MIN and MAX aggregate functions expect exactly one argument");
4287 const auto &arg = *info.args[0];
4296 auto _arg = env.compile(arg);
4297 Expr<T, L> _new_val = convert<Expr<T, L>>(_arg);
4298 if (_new_val.can_be_null()) {
4300 auto _new_val_pred =
4302 auto [new_val_, new_val_is_null_] = _new_val_pred.split();
4303 const Var<Bool<L>> new_val_is_null(new_val_is_null_);
4306 min_max =
Select(new_val_is_null,
4308 is_min ?
min(min_max, new_val_)
4309 :
max(min_max, new_val_));
4312 auto cmp = is_min ? new_val < min_max : new_val > min_max;
4314 min_max =
Select(new_val_is_null,
4320 IF (not new_val_is_null
and cmp) {
4327 auto neutral = is_min ? std::numeric_limits<T>::max()
4328 : std::numeric_limits<T>::lowest();
4329 auto _new_val_pred =
4331 auto new_val_ = _new_val_pred.insist_not_null();
4333 min_max = is_min ?
min(min_max, new_val_)
4334 :
max(min_max, new_val_);
4337 auto cmp = is_min ? new_val < min_max : new_val > min_max;
4351 []<
typename>() {
M_unreachable(
"invalid type for given number of SIMD lanes"); }
4353 auto &
n = as<const Numeric>(*info.entry.type);
4355 case Numeric::N_Int:
4356 case Numeric::N_Decimal:
4359 case 8: min_max.template operator()<int8_t >();
break;
4360 case 16: min_max.template operator()<int16_t>();
break;
4361 case 32: min_max.template operator()<int32_t>();
break;
4362 case 64: min_max.template operator()<int64_t>();
break;
4365 case Numeric::N_Float:
4367 min_max.template
operator()<
float>();
4369 min_max.template operator()<
double>();
4373 case m::Function::FN_AVG:
4375 case m::Function::FN_SUM: {
4376 M_insist(info.args.size() == 1,
"SUM aggregate function expects exactly one argument");
4377 const auto &arg = *info.args[0];
4387 auto _arg = env.compile(arg);
4388 Expr<T, L> _new_val = convert<Expr<T, L>>(_arg);
4389 if (_new_val.can_be_null()) {
4391 auto _new_val_pred =
4393 auto [new_val, new_val_is_null_] = _new_val_pred.split();
4394 const Var<Bool<L>> new_val_is_null(new_val_is_null_);
4396 sum +=
Select(new_val_is_null,
4401 auto _new_val_pred =
4403 sum += _new_val_pred.insist_not_null();
4407 []<
typename>() {
M_unreachable(
"invalid type for given number of SIMD lanes"); }
4409 auto &
n = as<const Numeric>(*info.entry.type);
4411 case Numeric::N_Int:
4412 case Numeric::N_Decimal:
4415 case 8: sum.template operator()<int8_t >();
break;
4416 case 16: sum.template operator()<int16_t>();
break;
4417 case 32: sum.template operator()<int32_t>();
break;
4418 case 64: sum.template operator()<int64_t>();
break;
4421 case Numeric::N_Float:
4423 sum.template
operator()<
float>();
4425 sum.template operator()<
double>();
4429 case m::Function::FN_COUNT: {
4430 M_insist(info.args.size() <= 1,
"COUNT aggregate function expects at most one argument");
4431 M_insist(info.entry.type->is_integral()
and info.entry.type->size() == 64);
4433 auto &count = *
M_notnull(std::get_if<
Var<I64<L>>>(&agg_values[idx]));
4435 if (info.args.empty()) {
4436 count += pred ? pred->template to<int64_t>() : I64<L>(1);
4438 auto _new_val = env.compile(*info.args[0]);
4441 I64<L> inc = pred ? (not_null<L>(_new_val)
and *pred).
template to<int64_t>()
4442 : not_null<L>(_new_val).template to<int64_t>();
4446 I64<L> inc = pred ? pred->template to<int64_t>() : I64<L>(1);
4456 for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
4457 auto &info = aggregates[idx];
4459 if (info.fnid == m::Function::FN_AVG) {
4460 M_insist(info.args.size() == 1,
"AVG aggregate function expects exactly one argument");
4461 const auto &arg = *info.args[0];
4462 M_insist(info.entry.type->is_double());
4464 auto it = avg_aggregates.find(info.entry.id);
4465 M_insist(it != avg_aggregates.end());
4466 const auto &avg_info = it->second;
4467 M_insist(avg_info.compute_running_avg,
4468 "AVG aggregate may only occur for running average computations");
4471 std::get_if<std::pair<
Var<Double<L>>,
Var<Bool<L>>>>(&agg_values[idx])
4476 auto running_count_idx = std::distance(
4477 aggregates.cbegin(),
4478 std::find_if(aggregates.cbegin(), aggregates.cend(), [&avg_info](
const auto &info){
4479 return info.entry.id == avg_info.running_count;
4482 M_insist(0 <= running_count_idx
and running_count_idx < aggregates.size());
4483 Double<L> running_count = [&](){
4484 auto &running_count =
4485 *
M_notnull(std::get_if<
Var<I64<L>>>(&agg_values[running_count_idx]));
4486 if constexpr (
L != 2) {
4487 return running_count.template to<double>();
4489 M_unreachable(
"conversion from `I64<2>` to `Double<2>` not supported");
4490 return Double<L>(0.0);
4494 auto _arg = env.compile(arg);
4495 _Double<L> _new_val = convert<_Double<L>>(_arg);
4496 if (_new_val.can_be_null()) {
4498 auto _new_val_pred = pred ?
Select(*pred, _new_val, _Double<L>::Null()) : _new_val;
4499 auto [new_val, new_val_is_null_] = _new_val_pred.split();
4500 const Var<Bool<L>> new_val_is_null(new_val_is_null_);
4502 auto delta_absolute = new_val - avg;
4503 auto delta_relative = delta_absolute / running_count;
4505 avg +=
Select(new_val_is_null,
4510 auto _new_val_pred = pred ?
Select(*pred, _new_val, avg) : _new_val;
4511 auto delta_absolute = _new_val_pred.insist_not_null() - avg;
4512 auto delta_relative = delta_absolute / running_count;
4514 avg += delta_relative;
4522 case 1: execute_pipeline.operator()<1>();
break;
4523 case 2: execute_pipeline.operator()<2>();
break;
4524 case 4: execute_pipeline.operator()<4>();
break;
4525 case 8: execute_pipeline.operator()<8>();
break;
4526 case 16: execute_pipeline.operator()<16>();
break;
4527 case 32: execute_pipeline.operator()<32>();
break;
4531 auto execute_teardown = [&]<std::size_t
L>(){
4534 "number of SIMD lanes in teardown callback must match the one in setup callback");
4540 auto agg_values =
static_cast<agg_t*
>(_agg_values);
4541 auto agg_value_backups =
static_cast<agg_backup_t*
>(_agg_value_backups);
4544 for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
4545 auto &info = aggregates[idx];
4547 bool is_min =
false;
4548 switch (info.fnid) {
4551 case m::Function::FN_MIN:
4553 case m::Function::FN_MAX: {
4554 auto min_max = [&]<
typename T>() {
4555 auto &[min_max_backup, is_null_backup] = *
M_notnull((
4558 >(&agg_value_backups[idx])
4560 std::tie(min_max_backup, is_null_backup) = *
M_notnull((
4564 auto &
n = as<const Numeric>(*info.entry.type);
4566 case Numeric::N_Int:
4567 case Numeric::N_Decimal:
4570 case 8: min_max.template operator()<int8_t >();
break;
4571 case 16: min_max.template operator()<int16_t>();
break;
4572 case 32: min_max.template operator()<int32_t>();
break;
4573 case 64: min_max.template operator()<int64_t>();
break;
4576 case Numeric::N_Float:
4578 min_max.template
operator()<
float>();
4580 min_max.template operator()<
double>();
4584 case m::Function::FN_AVG: {
4585 auto &[avg_backup, is_null_backup] = *
M_notnull((
4586 std::get_if<std::pair<
Global<Double<L>>,
Global<Bool<L>>>>(&agg_value_backups[idx])
4588 std::tie(avg_backup, is_null_backup) = *
M_notnull((
4589 std::get_if<std::pair<
Var<Double<L>>,
Var<Bool<L>>>>(&agg_values[idx])
4594 case m::Function::FN_SUM: {
4595 M_insist(info.args.size() == 1,
"SUM aggregate function expects exactly one argument");
4596 const auto &arg = *info.args[0];
4598 auto sum = [&]<
typename T>() {
4599 auto &[sum_backup, is_null_backup] = *
M_notnull((
4602 >(&agg_value_backups[idx])
4604 std::tie(sum_backup, is_null_backup) = *
M_notnull((
4608 auto &
n = as<const Numeric>(*info.entry.type);
4610 case Numeric::N_Int:
4611 case Numeric::N_Decimal:
4614 case 8: sum.template operator()<int8_t >();
break;
4615 case 16: sum.template operator()<int16_t>();
break;
4616 case 32: sum.template operator()<int32_t>();
break;
4617 case 64: sum.template operator()<int64_t>();
break;
4620 case Numeric::N_Float:
4622 sum.template
operator()<
float>();
4624 sum.template operator()<
double>();
4628 case m::Function::FN_COUNT: {
4629 auto &count_backup = *
M_notnull(std::get_if<
Global<I64<L>>>(&agg_value_backups[idx]));
4630 count_backup = *
M_notnull(std::get_if<
Var<I64<L>>>(&agg_values[idx]));
4638 for (std::size_t idx = 0; idx < aggregates.size(); ++idx) {
4639 agg_values[idx].~agg_t();
4640 agg_value_backups[idx].~agg_backup_t();
4644 delete[] agg_values;
4645 delete[] agg_value_backups;
4649 case 1: execute_teardown.operator()<1>();
break;
4650 case 2: execute_teardown.operator()<2>();
break;
4651 case 4: execute_teardown.operator()<4>();
break;
4652 case 8: execute_teardown.operator()<8>();
break;
4653 case 16: execute_teardown.operator()<16>();
break;
4654 case 32: execute_teardown.operator()<32>();
break;
4659 aggregation_child_pipeline();
4665 for (
auto &fn : finalize_aggregates)
4670 for (
auto &e : M.aggregation.schema().deduplicate()) {
4671 if (
auto it = avg_aggregates.find(e.id);
4672 it != avg_aggregates.end()
and not it->second.compute_running_avg)
4674 auto &avg_info = it->second;
4675 auto sum = results.
get(avg_info.sum);
4676 auto count = results.
get<_I64x1>(avg_info.running_count).insist_not_null().to<
double>();
4677 auto avg = convert<_Doublex1>(sum) / count;
4684 if (
value.can_be_null()) {
4693 [](
auto) ->
void {
M_unreachable(
"only scalar and non-string values must occur"); },
4694 [](std::monostate) ->
void {
M_unreachable(
"invalid reference"); },
4695 }, results.
get(e.id));
4712template<
bool CmpPredicated>
4725template<
bool CmpPredicated>
4735 for (
auto &o : M.sorting.order_by()) {
4737 if (orders.
find(
id) == orders.
cend())
4748template<
bool CmpPredicated>
4753 M_insist(
bool(M.materializing_factory),
"`wasm::Quicksort` must have a factory for the materialized child");
4754 const auto buffer_schema = M.child->get_matched_root().schema().drop_constants().deduplicate();
4755 const auto sorting_schema = M.sorting.schema().drop_constants().deduplicate();
4757 buffer_schema, *M.materializing_factory,
false, 0, std::move(setup), std::move(pipeline), std::move(teardown)
4761 FUNCTION(sorting_child_pipeline,
void(
void))
4771 sorting_child_pipeline();
4774 quicksort<CmpPredicated>(buffer, M.sorting.order_by());
4781 const std::tuple<const SortingOperator*> &partial_inner_nodes)
4789 for (
auto &o : std::get<0>(partial_inner_nodes)->order_by()) {
4791 if (orders.
find(
id) == orders.
cend())
4801 M.child->execute(std::move(setup), std::move(pipeline), std::move(teardown));
4809template<
bool Predicated>
4820template<
bool Predicated>
4823 std::vector<std::reference_wrapper<const ConditionSet>> &&post_cond_children)
4825 M_insist(post_cond_children.size() >= 2);
4827 ConditionSet post_cond(post_cond_children.back().get());
4837template<
bool Predicated>
4841 for (
auto &child : M.children)
4842 cost *= child->get_matched_root().info().estimated_cardinality;
4846template<
bool Predicated>
4850 const auto num_left_children = M.children.size() - 1;
4852 std::vector<Schema> schemas;
4853 schemas.reserve(num_left_children);
4854 std::vector<GlobalBuffer> buffers;
4855 buffers.reserve(num_left_children);
4858 for (std::size_t i = 0; i < num_left_children; ++i) {
4860 FUNCTION(nested_loop_join_child_pipeline,
void(
void))
4865 M_insist(
bool(M.materializing_factories_[i]),
4866 "`wasm::NestedLoopsJoin` must have a factory for each materialized child");
4867 const auto &schema = schemas.emplace_back(
4868 M.children[i]->get_matched_root().schema().drop_constants().deduplicate()
4872 buffers.emplace_back(
4874 *M.materializing_factories_[i],
4878 [&, pipeline=std::move(pipeline)](){
4879 if constexpr (Predicated) {
4880 CodeGenContext::Get().env().add_predicate(M.join.predicate());
4883 M_insist(CodeGenContext::Get().num_simd_lanes() == 1,
"invalid number of SIMD lanes");
4884 IF (CodeGenContext::Get().env().compile<_Boolx1>(M.join.predicate()).is_true_and_not_null()) {
4900 buffers.emplace_back(
4902 *M.materializing_factories_[i],
4906 [&](){ buffers.back().resume_pipeline_inline(); },
4912 M.children[i]->execute(
4914 [&](){ buffers.back().consume(); },
4918 nested_loop_join_child_pipeline();
4922 M.children.back()->execute(
4924 [&](){ buffers.back().resume_pipeline_inline(); },
4929template<
bool UniqueBuild,
bool Predicated>
4932 const std::tuple<const JoinOperator*, const Wildcard*, const Wildcard*> &partial_inner_nodes)
4937 auto &join = *std::get<0>(partial_inner_nodes);
4938 if (not join.predicate().is_equi())
4941 if constexpr (UniqueBuild) {
4943 auto &build = *std::get<1>(partial_inner_nodes);
4944 for (
auto &clause : join.predicate()) {
4945 M_insist(clause.size() == 1,
"invalid equi-predicate");
4946 auto &literal = clause[0];
4947 auto &
binary = as<const BinaryExpr>(literal.expr());
4949 (literal.negative()
and binary.tok == TK_BANG_EQUAL),
"invalid equi-predicate");
4950 M_insist(is<const Designator>(
binary.lhs),
"invalid equi-predicate");
4951 M_insist(is<const Designator>(
binary.rhs),
"invalid equi-predicate");
4953 const auto &entry_build = build.schema().has(id_first) ? build.schema()[id_first].second
4954 : build.schema()[id_second].second;
4957 if (not entry_build.unique())
4968template<
bool UniqueBuild,
bool Predicated>
4971 std::vector<std::reference_wrapper<const ConditionSet>> &&post_cond_children)
4973 M_insist(post_cond_children.size() == 2);
4988template<
bool UniqueBuild,
bool Predicated>
4992 return (M.build.id() == M.children[0]->get_matched_root().id() ? 1.0 : 2.0) + (UniqueBuild ? 0.0 : 0.1);
4994 return M.build.id() == M.children[1]->get_matched_root().id() ? 1.0 : 2.0 + (UniqueBuild ? 0.0 : 0.1);
4996 return 1.5 * M.build.info().estimated_cardinality +
4997 (UniqueBuild ? 1.0 : 1.1) * M.probe.info().estimated_cardinality;
5000template<
bool UniqueBuild,
bool Predicated>
5005 const uint64_t PAYLOAD_SIZE_THRESHOLD_IN_BITS =
5006 M.use_in_place_values ? std::numeric_limits<uint64_t>::max() : 0;
5008 M_insist(((M.join.schema() | M.join.predicate().get_required()) & M.build.schema()) == M.build.schema());
5009 M_insist(M.build.schema().drop_constants() == M.build.schema());
5010 const auto ht_schema = M.build.schema().deduplicate();
5016 std::vector<Schema::Identifier> payload_ids;
5017 uint64_t payload_size_in_bits = 0;
5018 for (
auto &e : ht_schema) {
5019 if (not
contains(build_keys, e.id)) {
5020 payload_ids.push_back(e.id);
5021 payload_size_in_bits += e.type->size();
5029 std::unique_ptr<HashTable> ht;
5030 std::vector<HashTable::index_t> build_key_indices;
5031 for (
auto &build_key : build_keys)
5032 build_key_indices.push_back(ht_schema[build_key].first);
5033 if (M.use_open_addressing_hashing) {
5034 if (payload_size_in_bits < PAYLOAD_SIZE_THRESHOLD_IN_BITS)
5035 ht = std::make_unique<GlobalOpenAddressingInPlaceHashTable>(ht_schema, std::move(build_key_indices),
5038 ht = std::make_unique<GlobalOpenAddressingOutOfPlaceHashTable>(ht_schema, std::move(build_key_indices),
5040 if (M.use_quadratic_probing)
5041 as<OpenAddressingHashTableBase>(*ht).set_probing_strategy<
QuadraticProbing>();
5043 as<OpenAddressingHashTableBase>(*ht).set_probing_strategy<
LinearProbing>();
5045 ht = std::make_unique<GlobalChainedHashTable>(ht_schema, std::move(build_key_indices), initial_capacity);
5049 FUNCTION(simple_hash_join_child_pipeline,
void(
void))
5053 M.children[0]->execute(
5056 ht->set_high_watermark(M.load_factor);
5061 std::optional<Boolx1> build_key_not_null;
5062 for (
auto &build_key : build_keys) {
5063 auto val = env.
get(build_key);
5064 if (build_key_not_null)
5065 build_key_not_null.emplace(*build_key_not_null
and not_null(val));
5067 build_key_not_null.emplace(
not_null(val));
5069 M_insist(
bool(build_key_not_null));
5070 IF (*build_key_not_null) {
5072 std::vector<SQL_t> key;
5073 for (
auto &build_key : build_keys)
5074 key.emplace_back(env.get(build_key));
5075 auto entry = ht->emplace(std::move(key));
5078 for (
auto &
id : payload_ids) {
5081 [](std::monostate) ->
void {
M_unreachable(
"invalid reference"); },
5082 }, entry.extract(
id));
5089 simple_hash_join_child_pipeline();
5091 M.children[1]->execute(
5092 setup_t(std::move(setup), [&](){ ht->setup(); }),
5093 [&, pipeline=std::move(pipeline)](){
5098 for (
auto &e : ht_schema) {
5099 if (not entry.has(e.id)) {
5101 M_insist(env.has(e.id),
"build key must already be contained in the current environment");
5108 if (
value.can_be_null()) {
5121 value.guarantees_terminating_nul()));
5123 [](std::monostate) ->
void {
M_unreachable(
"invalid reference"); },
5124 }, entry.extract(e.id));
5133 std::vector<SQL_t> key;
5134 for (
auto &probe_key : probe_keys)
5135 key.emplace_back(env.get(probe_key));
5136 if constexpr (UniqueBuild) {
5138 for (
auto build_it = build_keys.cbegin(), probe_it = probe_keys.cbegin(); build_it != build_keys.cend();
5139 ++build_it, ++probe_it)
5141 M_insist(probe_it != probe_keys.cend());
5142 if (not env.has(*build_it))
5143 env.add(*build_it, env.get(*probe_it));
5147 auto p = ht->find(std::move(key));
5148 auto &entry = p.first;
5149 auto &found = p.second;
5151 env.add_predicate(found);
5152 emit_tuple_and_resume_pipeline(std::move(entry));
5155 emit_tuple_and_resume_pipeline(std::move(entry));
5160 ht->for_each_in_equal_range(std::move(key), std::move(emit_tuple_and_resume_pipeline),
Predicated);
5163 teardown_t(std::move(teardown), [&](){ ht->teardown(); })
5167template<
bool SortLeft,
bool SortRight,
bool Predicated,
bool CmpPredicated>
5169 std::size_t child_idx,
5170 const std::tuple<const JoinOperator*, const Wildcard*, const Wildcard*> &partial_inner_nodes)
5175 auto &join = *std::get<0>(partial_inner_nodes);
5176 if (not join.predicate().is_equi())
5180 auto parent = std::get<1>(partial_inner_nodes);
5181 auto child = std::get<2>(partial_inner_nodes);
5184 std::vector<Schema::Identifier> keys_parent, keys_child;
5185 for (
auto &clause : join.predicate()) {
5186 M_insist(clause.size() == 1,
"invalid equi-predicate");
5187 auto &literal = clause[0];
5188 auto &
binary = as<const BinaryExpr>(literal.expr());
5190 (literal.negative()
and binary.tok == TK_BANG_EQUAL),
"invalid equi-predicate");
5191 M_insist(is<const Designator>(
binary.lhs),
"invalid equi-predicate");
5192 M_insist(is<const Designator>(
binary.rhs),
"invalid equi-predicate");
5195 const auto &[entry_parent, entry_child] = parent->schema().has(id_first)
5196 ? std::make_pair(parent->schema()[id_first].second, child_idx == 1 ? child->schema()[id_second].second : std::move(dummy))
5197 : std::make_pair(parent->schema()[id_second].second, child_idx == 1 ? child->schema()[id_first].second : std::move(dummy));
5198 keys_parent.push_back(entry_parent.id);
5199 keys_child.push_back(entry_child.id);
5202 if (not entry_parent.unique())
5205 M_insist(keys_parent.size() == keys_child.size(),
"number of found IDs differ");
5206 M_insist(not keys_parent.empty(),
"must find at least one ID");
5208 if constexpr (not SortLeft or not SortRight) {
5212 if (not SortLeft
and child_idx == 0) {
5213 for (
auto &key_parent : keys_parent) {
5214 if (orders.
find(key_parent) == orders.
cend())
5217 }
else if (not SortRight
and child_idx == 1) {
5218 for (
auto &key_child : keys_child) {
5219 if (orders.
find(key_child) == orders.
cend())
5232template<
bool SortLeft,
bool SortRight,
bool Predicated,
bool CmpPredicated>
5235 std::vector<std::reference_wrapper<const ConditionSet>> &&post_cond_children)
5237 M_insist(post_cond_children.size() == 2);
5250 if constexpr (not SortLeft) {
5251 Sortedness sorting_left(post_cond_children[0].get().get_condition<Sortedness>());
5254 if constexpr (not SortRight) {
5255 Sortedness sorting_right(post_cond_children[1].get().get_condition<Sortedness>());
5258 if constexpr (SortLeft or SortRight) {
5263 if constexpr (SortLeft) {
5264 for (
auto &key_parent : keys_parent) {
5265 if (orders.
find(key_parent) == orders.
cend())
5269 if constexpr (SortRight) {
5270 for (
auto &key_child : keys_child) {
5271 if (orders.
find(key_child) == orders.
cend())
5281template<
bool SortLeft,
bool SortRight,
bool Predicated,
bool CmpPredicated>
5284 const double card_left = M.parent.info().estimated_cardinality;
5285 const double card_right = M.child.info().estimated_cardinality;
5287 double cost = card_left + card_right;
5288 if constexpr (SortLeft)
5289 cost += std::log2(card_left) * card_left;
5290 if constexpr (SortRight)
5291 cost += std::log2(card_right) * card_right;
5296template<
bool SortLeft,
bool SortRight,
bool Predicated,
bool CmpPredicated>
5304 const bool needs_buffer_parent = not is<const ScanOperator>(M.parent) or SortLeft;
5305 const bool needs_buffer_child = not is<const ScanOperator>(M.child) or SortRight;
5308 M_insist(
bool(M.left_materializing_factory),
5309 "`wasm::SortMergeJoin` must have a factory for the materialized left child");
5310 M_insist(
bool(M.right_materializing_factory),
5311 "`wasm::SortMergeJoin` must have a factory for the materialized right child");
5312 const auto schema_parent = M.parent.schema().drop_constants().deduplicate();
5313 const auto schema_child = M.child.schema().drop_constants().deduplicate();
5314 std::optional<GlobalBuffer> buffer_parent, buffer_child;
5315 if (needs_buffer_parent)
5316 buffer_parent.emplace(schema_parent, *M.left_materializing_factory);
5317 if (needs_buffer_child)
5318 buffer_child.emplace(schema_child, *M.right_materializing_factory);
5321 if (needs_buffer_parent) {
5322 FUNCTION(sort_merge_join_parent_pipeline,
void(
void))
5325 M.children[0]->execute(
5327 [&](){ buffer_parent->consume(); },
5331 sort_merge_join_parent_pipeline();
5333 if (needs_buffer_child) {
5334 FUNCTION(sort_merge_join_child_pipeline,
void(
void))
5337 M.children[1]->execute(
5339 [&](){ buffer_child->consume(); },
5343 sort_merge_join_child_pipeline();
5347 std::vector<SortingOperator::order_type> order_parent, order_child;
5348 for (
auto &clause : M.join.predicate()) {
5349 M_insist(clause.size() == 1,
"invalid equi-predicate");
5350 auto &literal = clause[0];
5351 auto &
binary = as<const BinaryExpr>(literal.expr());
5353 (literal.negative()
and binary.tok == TK_BANG_EQUAL),
"invalid equi-predicate");
5354 M_insist(is<const Designator>(
binary.lhs),
"invalid equi-predicate");
5355 M_insist(is<const Designator>(
binary.rhs),
"invalid equi-predicate");
5358 order_parent.emplace_back(*expr_parent,
true);
5359 order_child.emplace_back(*expr_child,
true);
5361 M_insist(order_parent.size() == order_child.size(),
"number of found IDs differ");
5362 M_insist(not order_parent.empty(),
"must find at least one ID");
5365 if constexpr (SortLeft)
5366 quicksort<CmpPredicated>(*buffer_parent, order_parent);
5367 if constexpr (SortRight)
5368 quicksort<CmpPredicated>(*buffer_child, order_child);
5371 auto child_smaller_equal = [&]() -> Boolx1 {
5372 std::optional<Boolx1> child_smaller_equal_;
5373 for (std::size_t i = 0; i < order_child.size(); ++i) {
5374 auto &des_parent = as<const Designator>(order_parent[i].first);
5375 auto &des_child = as<const Designator>(order_child[i].first);
5377 auto cpy_parent = std::make_unique<Designator>(des_parent.tok, des_parent.table_name, des_parent.attr_name,
5378 des_parent.type(), des_parent.target());
5379 auto cpy_child = std::make_unique<Designator>(des_child.tok, des_child.table_name, des_child.attr_name,
5380 des_child.type(), des_child.target());
5381 BinaryExpr expr(std::move(leq), std::move(cpy_child), std::move(cpy_parent));
5384 Boolx1 cmp = env.compile<_Boolx1>(
expr).is_true_and_not_null();
5385 if (child_smaller_equal_)
5386 child_smaller_equal_.emplace(*child_smaller_equal_
and (
is_null(child) or cmp));
5388 child_smaller_equal_.emplace(
is_null(child) or cmp);
5390 M_insist(
bool(child_smaller_equal_));
5391 return *child_smaller_equal_;
5395 static Schema empty_schema;
5397 auto [inits_parent, loads_parent, _jumps_parent] = [&](){
5398 if (needs_buffer_parent) {
5400 buffer_parent->layout(), 1, buffer_parent->schema(), tuple_id_parent);
5402 auto &scan = as<const ScanOperator>(M.parent);
5404 scan.store().table().layout(), 1, scan.store().table().schema(scan.alias()),
5408 auto [inits_child, loads_child, _jumps_child] = [&](){
5409 if (needs_buffer_child) {
5411 buffer_child->layout(), 1, buffer_child->schema(), tuple_id_child);
5413 auto &scan = as<const ScanOperator>(M.child);
5415 scan.store().table().layout(), 1, scan.store().table().schema(scan.alias()),
5420 Block jumps_parent(std::move(_jumps_parent)), jumps_child(std::move(_jumps_child));
5424 inits_parent.attach_to_current();
5425 inits_child.attach_to_current();
5426 U64x1 size_parent = needs_buffer_parent ? buffer_parent->size()
5427 :
get_num_rows(as<const ScanOperator>(M.parent).store().table().name());
5428 U64x1 size_child = needs_buffer_child ? buffer_child->size()
5429 :
get_num_rows(as<const ScanOperator>(M.child).store().table().name());
5430 WHILE (tuple_id_parent < size_parent
and tuple_id_child < size_child) {
5431 loads_parent.attach_to_current();
5432 loads_child.attach_to_current();
5434 env.add_predicate(M.join.predicate());
5438 IF (env.compile<_Boolx1>(M.join.predicate()).is_true_and_not_null()) {
5442 IF (child_smaller_equal()) {
5445 jumps_parent.attach_to_current();
5470 std::optional<Block> teardown_block;
5471 std::optional<BlockUser> use_teardown;
5473 std::optional<Var<U64x1>> counter;
5478 setup_t(std::move(setup), [&](){
5479 counter.emplace(counter_backup);
5480 teardown_block.emplace(
"limit.teardown",
true);
5481 use_teardown.emplace(*teardown_block);
5483 [&, pipeline=std::move(pipeline)](){
5486 const uint64_t limit = M.limit.offset() + M.limit.limit();
5489 IF (*counter >= limit) {
5490 GOTO(*teardown_block);
5494 if (M.limit.offset()) {
5495 IF (*counter >= uint64_t(M.limit.offset())) {
5496 Wasm_insist(*counter < limit,
"counter must not exceed limit");
5500 Wasm_insist(*counter < limit,
"counter must not exceed limit");
5510 use_teardown.reset();
5511 teardown_block.reset();
5514 counter_backup = *counter;
5526 std::size_t child_idx,
5527 const std::tuple<const GroupingOperator*, const JoinOperator*, const Wildcard*, const Wildcard*>
5528 &partial_inner_nodes)
5533 auto &grouping = *std::get<0>(partial_inner_nodes);
5534 for (
auto &fn_expr : grouping.aggregates()) {
5535 M_insist(fn_expr.get().args.size() <= 1);
5536 if (fn_expr.get().args.size() == 1
and not is<const Designator>(fn_expr.get().args[0]))
5541 auto &join = *std::get<1>(partial_inner_nodes);
5542 if (not join.predicate().is_equi())
5546 if (child_idx == 0) {
5548 auto &build = *std::get<2>(partial_inner_nodes);
5552 const auto num_grouping_keys = grouping.group_by().size();
5553 if (num_grouping_keys != build_keys.size())
5555 for (std::size_t i = 0; i < num_grouping_keys; ++i) {
5557 if (not
contains(build_keys, grouping_key))
5570 return 1.5 * M.build.info().estimated_cardinality + 1.0 * M.probe.info().estimated_cardinality +
5571 1.0 * M.join.info().estimated_cardinality;
5591 const uint64_t AGGREGATES_SIZE_THRESHOLD_IN_BITS =
5592 M.use_in_place_values ? std::numeric_limits<uint64_t>::max() : 0;
5595 const auto num_keys = M.grouping.group_by().size();
5599 for (std::size_t i = 0; i < num_keys; ++i) {
5600 auto &e = M.grouping.schema()[i];
5601 ht_schema.
add(e.id, e.type, e.constraints);
5604 const auto &aggregates = aggregates_info.first;
5605 const auto &avg_aggregates = aggregates_info.second;
5606 bool needs_build_counter =
false;
5607 uint64_t aggregates_size_in_bits = 0;
5608 for (
auto &info : aggregates) {
5609 ht_schema.
add(info.entry);
5610 aggregates_size_in_bits += info.entry.type->size();
5613 if (info.fnid == m::Function::FN_COUNT or info.fnid == m::Function::FN_SUM) {
5614 if (not info.args.empty()) {
5615 M_insist(info.args.size() == 1,
"aggregate functions expect at most one argument");
5616 auto &des = as<const Designator>(*info.args[0]);
5618 if (M.probe.schema().has(arg))
5619 needs_build_counter =
true;
5623 if (needs_build_counter) {
5626 aggregates_size_in_bits += 64;
5630 aggregates_size_in_bits += 64;
5634 M_insist(build_keys.size() == num_keys);
5640 std::unique_ptr<HashTable> ht;
5641 std::vector<HashTable::index_t> key_indices(num_keys);
5642 std::iota(key_indices.begin(), key_indices.end(), 0);
5643 if (M.use_open_addressing_hashing) {
5644 if (aggregates_size_in_bits < AGGREGATES_SIZE_THRESHOLD_IN_BITS)
5645 ht = std::make_unique<GlobalOpenAddressingInPlaceHashTable>(ht_schema, std::move(key_indices),
5648 ht = std::make_unique<GlobalOpenAddressingOutOfPlaceHashTable>(ht_schema, std::move(key_indices),
5650 if (M.use_quadratic_probing)
5651 as<OpenAddressingHashTableBase>(*ht).set_probing_strategy<
QuadraticProbing>();
5653 as<OpenAddressingHashTableBase>(*ht).set_probing_strategy<
LinearProbing>();
5655 ht = std::make_unique<GlobalChainedHashTable>(ht_schema, std::move(key_indices), initial_capacity);
5658 std::optional<HashTable::entry_t> dummy;
5667 bool build_phase) -> std::tuple<Block, Block, Block>
5669 Block init_aggs(
"hash_based_group_join.init_aggs",
false),
5670 update_aggs(
"hash_based_group_join.update_aggs",
false),
5671 update_avg_aggs(
"hash_based_group_join.update_avg_aggs",
false);
5672 for (
auto &info : aggregates) {
5673 bool is_min =
false;
5674 switch (info.fnid) {
5677 case m::Function::FN_MIN:
5679 case m::Function::FN_MAX: {
5680 M_insist(info.args.size() == 1,
"MIN and MAX aggregate functions expect exactly one argument");
5681 auto &arg = as<const Designator>(*info.args[0]);
5683 arg.attr_name.text.assert_not_none()));
5687 requires (not (std::same_as<_T, _Boolx1> or std::same_as<_T, NChar>)) {
5688 using type =
typename _T::type;
5693 auto neutral = is_min ?
T(std::numeric_limits<type>::max())
5694 :
T(std::numeric_limits<type>::lowest());
5696 auto _arg = env.compile(arg);
5697 auto [val_,
is_null] = convert<_T>(_arg).split();
5700 r.clone().set_value(neutral);
5701 if (info.entry.nullable())
5702 r.clone().set_null_bit(Boolx1(
true));
5704 r.clone().set_value(val);
5705 if (info.entry.nullable())
5706 r.clone().set_null_bit(Boolx1(
false));
5709 r.clone().set_value(neutral);
5710 if (info.entry.nullable())
5711 r.clone().set_null_bit(Boolx1(
true));
5720 auto _arg = env.compile(arg);
5721 _T _new_val = convert<_T>(_arg);
5722 if (_new_val.can_be_null()) {
5723 auto [new_val_, new_val_is_null_] = _new_val.split();
5724 auto [old_min_max_, old_min_max_is_null] = _T(r.clone()).split();
5725 const Var<Boolx1> new_val_is_null(new_val_is_null_);
5727 auto chosen_r =
Select(new_val_is_null, dummy->extract<_T>(info.entry.id), r.clone());
5728 if constexpr (std::floating_point<type>) {
5730 is_min ?
min(old_min_max_, new_val_)
5731 :
max(old_min_max_, new_val_)
5734 const Var<T> new_val(new_val_),
5735 old_min_max(old_min_max_);
5736 auto cmp = is_min ? new_val < old_min_max : new_val > old_min_max;
5744 old_min_max_is_null
and new_val_is_null
5747 auto new_val_ = _new_val.insist_not_null();
5748 auto old_min_max_ = _T(r.clone()).insist_not_null();
5749 if constexpr (std::floating_point<type>) {
5751 is_min ?
min(old_min_max_, new_val_)
5752 :
max(old_min_max_, new_val_)
5755 const Var<T> new_val(new_val_),
5756 old_min_max(old_min_max_);
5757 auto cmp = is_min ? new_val < old_min_max : new_val > old_min_max;
5769 requires std::same_as<_T,_Boolx1> or std::same_as<_T, NChar> {
5772 [](std::monostate) ->
void {
M_unreachable(
"invalid reference"); },
5773 }, entry.
extract(info.entry.id));
5776 case m::Function::FN_AVG: {
5777 auto it = avg_aggregates.find(info.entry.id);
5778 M_insist(it != avg_aggregates.end());
5779 const auto &avg_info = it->second;
5780 M_insist(avg_info.compute_running_avg,
5781 "AVG aggregate may only occur for running average computations");
5782 M_insist(info.args.size() == 1,
"AVG aggregate function expects exactly one argument");
5783 auto &arg = as<const Designator>(*info.args[0]);
5785 arg.attr_name.text.assert_not_none()));
5787 auto r = entry.
extract<_Doublex1>(info.entry.id);
5792 auto _arg = env.compile(arg);
5793 auto [val_,
is_null] = convert<_Doublex1>(_arg).split();
5796 r.clone().set_value(Doublex1(0.0));
5797 if (info.entry.nullable())
5798 r.clone().set_null_bit(Boolx1(
true));
5800 r.clone().set_value(val);
5801 if (info.entry.nullable())
5802 r.clone().set_null_bit(Boolx1(
false));
5805 r.clone().set_value(Doublex1(0.0));
5806 if (info.entry.nullable())
5807 r.clone().set_null_bit(Boolx1(
true));
5818 auto _arg = env.compile(arg);
5819 _Doublex1 _new_val = convert<_Doublex1>(_arg);
5820 if (_new_val.can_be_null()) {
5821 auto [new_val, new_val_is_null_] = _new_val.split();
5822 auto [old_avg_, old_avg_is_null] = _Doublex1(r.clone()).split();
5823 const Var<Boolx1> new_val_is_null(new_val_is_null_);
5826 auto delta_absolute = new_val - old_avg;
5827 auto running_count = _I64x1(entry.
get<_I64x1>(avg_info.running_count)).insist_not_null();
5828 auto delta_relative = delta_absolute / running_count.to<
double>();
5830 auto chosen_r =
Select(new_val_is_null, dummy->extract<_Doublex1>(info.entry.id), r.clone());
5832 old_avg + delta_relative
5835 old_avg_is_null
and new_val_is_null
5838 auto new_val = _new_val.insist_not_null();
5839 auto old_avg_ = _Doublex1(r.clone()).insist_not_null();
5842 auto delta_absolute = new_val - old_avg;
5843 auto running_count = _I64x1(entry.
get<_I64x1>(avg_info.running_count)).insist_not_null();
5844 auto delta_relative = delta_absolute / running_count.to<
double>();
5846 old_avg + delta_relative
5853 case m::Function::FN_SUM: {
5854 M_insist(info.args.size() == 1,
"SUM aggregate function expects exactly one argument");
5855 auto &arg = as<const Designator>(*info.args[0]);
5857 arg.attr_name.text.assert_not_none()));
5861 requires (not (std::same_as<_T, _Boolx1> or std::same_as<_T, NChar>)) {
5862 using type =
typename _T::type;
5868 auto _arg = env.compile(arg);
5869 auto [val_,
is_null] = convert<_T>(_arg).split();
5872 r.clone().set_value(
T(type(0)));
5873 if (info.entry.nullable())
5874 r.clone().set_null_bit(Boolx1(
true));
5876 r.clone().set_value(val);
5877 if (info.entry.nullable())
5878 r.clone().set_null_bit(Boolx1(
false));
5881 r.clone().set_value(
T(type(0)));
5882 if (info.entry.nullable())
5883 r.clone().set_null_bit(Boolx1(
true));
5892 auto _arg = env.compile(arg);
5893 _T _new_val = convert<_T>(_arg);
5894 if (_new_val.can_be_null()) {
5895 auto [new_val, new_val_is_null_] = _new_val.split();
5896 auto [old_sum, old_sum_is_null] = _T(r.clone()).split();
5897 const Var<Boolx1> new_val_is_null(new_val_is_null_);
5899 auto chosen_r =
Select(new_val_is_null, dummy->extract<_T>(info.entry.id), r.clone());
5904 old_sum_is_null
and new_val_is_null
5907 auto new_val = _new_val.insist_not_null();
5908 auto old_sum = _T(r.clone()).insist_not_null();
5917 requires std::same_as<_T,_Boolx1> or std::same_as<_T, NChar> {
5920 [](std::monostate) ->
void {
M_unreachable(
"invalid reference"); },
5921 }, entry.
extract(info.entry.id));
5924 case m::Function::FN_COUNT: {
5925 M_insist(info.args.size() <= 1,
"COUNT aggregate function expects at most one argument");
5927 auto r = entry.
get<_I64x1>(info.entry.id);
5929 if (info.args.empty()) {
5930 if (not build_phase) {
5935 r.clone() = _I64x1(1);
5938 auto old_count = _I64x1(r.clone()).insist_not_null();
5940 old_count + int64_t(1)
5945 auto &arg = as<const Designator>(*info.args[0]);
5947 arg.attr_name.text.assert_not_none()));
5952 auto _arg = env.compile(arg);
5953 I64x1 new_val_not_null =
5956 r.clone() = _I64x1(new_val_not_null);
5958 r.clone() = _I64x1(0);
5967 auto _arg = env.compile(arg);
5968 I64x1 new_val_not_null =
5971 auto old_count = _I64x1(r.clone()).insist_not_null();
5973 old_count + new_val_not_null
5982 return { std::move(init_aggs), std::move(update_aggs), std::move(update_avg_aggs) };
5986 FUNCTION(hash_based_group_join_build_child_pipeline,
void(
void))
5990 M.children[0]->execute(
5993 ht->set_high_watermark(M.load_factor);
5994 dummy.emplace(ht->dummy_entry());
6000 std::optional<Boolx1> build_key_not_null;
6001 for (
auto &build_key : build_keys) {
6002 auto val = env.
get(build_key);
6003 if (build_key_not_null)
6004 build_key_not_null.emplace(*build_key_not_null
and not_null(val));
6006 build_key_not_null.emplace(
not_null(val));
6008 M_insist(
bool(build_key_not_null));
6009 IF (*build_key_not_null) {
6011 std::vector<SQL_t> key;
6012 for (
auto &build_key : build_keys)
6013 key.emplace_back(env.get(build_key));
6014 auto [entry, inserted] = ht->try_emplace(std::move(key));
6017 auto t = compile_aggregates(entry, env, M.build.schema(),
true);
6018 auto &init_aggs = std::get<0>(t);
6019 auto &update_aggs = std::get<1>(t);
6020 auto &update_avg_aggs = std::get<2>(t);
6023 if (needs_build_counter) {
6024 auto r = entry.
extract<_I64x1>(C.
pool(
"$build_counter"));
6026 r.clone() = _I64x1(1);
6029 auto old_count = _I64x1(r.clone()).insist_not_null();
6031 old_count + int64_t(1)
6037 auto r = entry.
extract<_I64x1>(C.
pool(
"$probe_counter"));
6043 init_aggs.attach_to_current();
6045 update_aggs.attach_to_current();
6046 update_avg_aggs.attach_to_current();
6053 hash_based_group_join_build_child_pipeline();
6056 FUNCTION(hash_based_group_join_probe_child_pipeline,
void(
void))
6060 M.children[1]->execute(
6063 dummy.emplace(ht->dummy_entry());
6071 std::vector<SQL_t> key;
6072 for (
auto &probe_key : probe_keys)
6073 key.emplace_back(env.get(probe_key));
6074 auto [entry, found] = ht->find(std::move(key));
6077 auto t = compile_aggregates(entry, env, M.probe.schema(),
false);
6078 auto &init_aggs = std::get<0>(t);
6079 auto &update_aggs = std::get<1>(t);
6080 auto &update_avg_aggs = std::get<2>(t);
6084 auto r = entry.
extract<_I64x1>(C.
pool(
"$probe_counter"));
6085 auto old_count = _I64x1(r.clone()).insist_not_null();
6087 old_count + int64_t(1)
6093 M_insist(init_aggs.empty(),
"aggregates must be initialized in build phase");
6095 update_aggs.attach_to_current();
6096 update_avg_aggs.attach_to_current();
6102 hash_based_group_join_probe_child_pipeline();
6107 setup_t(std::move(setup), [&](){ ht->setup(); })();
6110 I64x1 probe_counter = _I64x1(entry.
get<_I64x1>(C.
pool(
"$probe_counter"))).insist_not_null();
6111 IF (probe_counter != int64_t(0)) {
6114 for (std::size_t i = 0; i < num_keys; ++i) {
6115 auto &e = M.grouping.schema()[i];
6116 key_schema.
add(e.id, e.type, e.constraints);
6120 for (
auto &e : M.grouping.schema().deduplicate()) {
6122 key_schema.
find(e.id);
6127 if (
auto it = avg_aggregates.find(e.id);
6128 it != avg_aggregates.end()
and not it->second.compute_running_avg)
6130 auto &avg_info = it->second;
6133 requires (std::same_as<T, _I64x1> or std::same_as<T, _Doublex1>) {
6134 return T(r).template to<double>();
6137 [](std::monostate&&) -> _Doublex1 {
M_unreachable(
"invalid reference"); },
6138 }, entry.
get(avg_info.sum));
6139 auto count = _I64x1(entry.
get<_I64x1>(avg_info.running_count)).insist_not_null().to<
double>();
6140 auto avg = sum / count;
6141 if (avg.can_be_null()) {
6147 env.add(e.id, _Doublex1(var));
6154 auto pred = [&e](
const auto &info) ->
bool {
return info.entry.id == e.id; };
6155 if (
auto it = std::find_if(aggregates.cbegin(), aggregates.cend(), pred);
6156 it != aggregates.cend())
6160 if (it->args.empty()) {
6161 M_insist(it->fnid == m::Function::FN_COUNT,
6162 "only COUNT aggregate function may have no argument");
6163 I64x1 probe_counter =
6164 _I64x1(entry.
get<_I64x1>(C.
pool(
"$probe_counter"))).insist_not_null();
6170 M_insist(it->args.size() == 1,
"aggregate functions expect at most one argument");
6171 auto &des = as<const Designator>(*it->args[0]);
6173 if (it->fnid == m::Function::FN_COUNT or it->fnid == m::Function::FN_SUM) {
6174 if (M.probe.schema().has(arg)) {
6175 I64x1 build_counter =
6176 _I64x1(entry.
get<_I64x1>(C.
pool(
"$build_counter"))).insist_not_null();
6177 auto agg =
value * build_counter.to<
T>();
6178 if (agg.can_be_null()) {
6187 M_insist(M.build.schema().has(arg),
6188 "argument ID must occur in either child schema");
6189 I64x1 probe_counter =
6190 _I64x1(entry.
get<_I64x1>(C.
pool(
"$probe_counter"))).insist_not_null();
6191 auto agg =
value * probe_counter.to<
T>();
6192 if (agg.can_be_null()) {
6207 if (
value.can_be_null()) {
6218 auto pred = [&e](
const auto &info) ->
bool {
return info.entry.id == e.id; };
6219 M_insist(std::find_if(aggregates.cbegin(), aggregates.cend(), pred) == aggregates.cend(),
6220 "booleans must not be the result of aggregate functions");
6223 if (
value.can_be_null()) {
6229 env.add(e.id, _Boolx1(var));
6234 auto pred = [&e](
const auto &info) ->
bool {
return info.entry.id == e.id; };
6235 M_insist(std::find_if(aggregates.cbegin(), aggregates.cend(), pred) == aggregates.cend(),
6236 "strings must not be the result of aggregate functions");
6241 value.guarantees_terminating_nul()));
6243 [](std::monostate&&) ->
void {
M_unreachable(
"invalid reference"); },
6244 }, entry.
get(e.id));
6252 teardown_t(std::move(teardown), [&](){ ht->teardown(); })();
6273 indent(out, level) <<
"wasm::NoOp" <<
print_info(this->noop) <<
" (cumulative cost " << cost() <<
')';
6274 this->child->print(out, level + 1);
6277template<
bool SIMDfied>
6281 << this->callback.schema() <<
print_info(this->callback)
6282 <<
" (cumulative cost " << cost() <<
')';
6283 this->child->print(out, level + 1);
6286template<
bool SIMDfied>
6290 << this->print_op.schema() <<
print_info(this->print_op)
6291 <<
" (cumulative cost " << cost() <<
')';
6292 this->child->print(out, level + 1);
6295template<
bool SIMDfied>
6298 indent(out, level) << (SIMDfied ?
"wasm::SIMDScan(" :
"wasm::Scan(") << this->scan.alias() <<
") ";
6299 if (this->buffer_factory_
and this->scan.schema().drop_constants().deduplicate().num_entries())
6300 out <<
"with " << this->buffer_num_tuples_ <<
" tuples output buffer ";
6301 out << this->scan.schema() <<
print_info(this->scan) <<
" (cumulative cost " << cost() <<
')';
6304template<
idx::IndexMethod IndexMethod>
6308 indent(out, level) <<
"wasm::ArrayIndexScan(";
6310 indent(out, level) <<
"wasm::RecursiveModelIndexScan(";
6315 out <<
"Compilation[";
6319 out <<
"ExposedMemory";
6323 out <<
"Interpretation[";
6341 out <<
"ExposedMemory";
6348 out <<
"], " << this->scan.alias() <<
", " << this->filter.filter() <<
") ";
6349 if (this->buffer_factory_
and this->scan.schema().drop_constants().deduplicate().num_entries())
6350 out <<
"with " << this->buffer_num_tuples_ <<
" tuples output buffer ";
6351 out << this->scan.schema() <<
print_info(this->scan) <<
" (cumulative cost " << cost() <<
')';
6354template<
bool Predicated>
6357 indent(out, level) <<
"wasm::" << (
Predicated ?
"Predicated" :
"Branching") <<
"Filter ";
6358 if (this->buffer_factory_
and this->filter.schema().drop_constants().deduplicate().num_entries())
6359 out <<
"with " << this->buffer_num_tuples_ <<
" tuples output buffer ";
6360 out << this->filter.schema() <<
print_info(this->filter) <<
" (cumulative cost " << cost() <<
')';
6361 this->child->print(out, level + 1);
6366 indent(out, level) <<
"wasm::LazyDisjunctiveFilter ";
6367 if (this->buffer_factory_
and this->filter.schema().drop_constants().deduplicate().num_entries())
6368 out <<
"with " << this->buffer_num_tuples_ <<
" tuples output buffer ";
6369 const cnf::Clause &clause = this->filter.filter()[0];
6370 for (
auto it = clause.cbegin(); it != clause.cend(); ++it) {
6371 if (it != clause.cbegin()) out <<
" → ";
6374 out <<
' ' << this->filter.schema() <<
print_info(this->filter) <<
" (cumulative cost " << cost() <<
')';
6375 this->child->print(out, level + 1);
6380 indent(out, level) <<
"wasm::Projection ";
6381 if (this->buffer_factory_
and this->projection.schema().drop_constants().deduplicate().num_entries())
6382 out <<
"with " << this->buffer_num_tuples_ <<
" tuples output buffer ";
6383 out << this->projection.schema() <<
print_info(this->projection) <<
" (cumulative cost " << cost() <<
')';
6385 this->child->get()->print(out, level + 1);
6390 indent(out, level) <<
"wasm::HashBasedGrouping " << this->grouping.schema() <<
print_info(this->grouping)
6391 <<
" (cumulative cost " << cost() <<
')';
6392 this->child->print(out, level + 1);
6397 indent(out, level) <<
"wasm::OrderedGrouping " << this->grouping.schema() <<
print_info(this->grouping)
6398 <<
" (cumulative cost " << cost() <<
')';
6399 this->child->print(out, level + 1);
6404 indent(out, level) <<
"wasm::Aggregation " << this->aggregation.schema() <<
print_info(this->aggregation)
6405 <<
" (cumulative cost " << cost() <<
')';
6406 this->child->print(out, level + 1);
6409template<
bool CmpPredicated>
6412 indent(out, level) <<
"wasm::" << (CmpPredicated ?
"Predicated" :
"") <<
"Quicksort " << this->sorting.schema()
6413 <<
print_info(this->sorting) <<
" (cumulative cost " << cost() <<
')';
6414 this->child->print(out, level + 1);
6419 indent(out, level) <<
"wasm::NoOpSorting" <<
print_info(this->sorting) <<
" (cumulative cost " << cost() <<
')';
6420 this->child->print(out, level + 1);
6423template<
bool Predicated>
6426 indent(out, level) <<
"wasm::" << (
Predicated ?
"Predicated" :
"") <<
"NestedLoopsJoin ";
6427 if (this->buffer_factory_
and this->join.schema().drop_constants().deduplicate().num_entries())
6428 out <<
"with " << this->buffer_num_tuples_ <<
" tuples output buffer ";
6429 out << this->join.schema() <<
print_info(this->join) <<
" (cumulative cost " << cost() <<
')';
6432 std::size_t i = this->children.size();
6435 indent(out, level) << i <<
". input";
6436 child.
print(out, level + 1);
6440template<
bool Unique,
bool Predicated>
6443 indent(out, level) <<
"wasm::" << (
Predicated ?
"Predicated" :
"") <<
"SimpleHashJoin";
6444 if (Unique) out <<
" on UNIQUE key ";
6445 if (this->buffer_factory_
and this->join.schema().drop_constants().deduplicate().num_entries())
6446 out <<
"with " << this->buffer_num_tuples_ <<
" tuples output buffer ";
6447 out << this->join.schema() <<
print_info(this->join) <<
" (cumulative cost " << cost() <<
')';
6452 indent(out, level) <<
"probe input";
6453 probe.
print(out, level + 1);
6454 indent(out, level) <<
"build input";
6455 build.
print(out, level + 1);
6458template<
bool SortLeft,
bool SortRight,
bool Predicated,
bool CmpPredicated>
6460 unsigned level)
const
6462 indent(out, level) <<
"wasm::" << (
Predicated ?
"Predicated" :
"") <<
"SortMergeJoin ";
6463 switch ((
unsigned(SortLeft) << 1) | unsigned(SortRight))
6465 case 0: out <<
"pre-sorted ";
break;
6466 case 1: out <<
"sorting right input " << (CmpPredicated ?
"predicated " :
"");
break;
6467 case 2: out <<
"sorting left input " << (CmpPredicated ?
"predicated " :
"");
break;
6468 case 3: out <<
"sorting both inputs " << (CmpPredicated ?
"predicated " :
"");
break;
6470 const bool needs_buffer_parent = not is<const ScanOperator>(this->parent) or SortLeft;
6471 const bool needs_buffer_child = not is<const ScanOperator>(this->child) or SortRight;
6472 if (needs_buffer_parent
and needs_buffer_child)
6473 out <<
"and materializing both inputs ";
6474 else if (needs_buffer_parent)
6475 out <<
"and materializing left input ";
6476 else if (needs_buffer_child)
6477 out <<
"and materializing right input ";
6478 out << this->join.schema() <<
print_info(this->join) <<
" (cumulative cost " << cost() <<
')';
6483 indent(out, level) <<
"right input";
6484 right.
print(out, level + 1);
6485 indent(out, level) <<
"left input";
6486 left.
print(out, level + 1);
6491 indent(out, level) <<
"wasm::Limit " << this->limit.schema() <<
print_info(this->limit)
6492 <<
" (cumulative cost " << cost() <<
')';
6493 this->child->print(out, level + 1);
6498 indent(out, level) <<
"wasm::HashBasedGroupJoin ";
6499 if (this->buffer_factory_
and this->grouping.schema().drop_constants().deduplicate().num_entries())
6500 out <<
"with " << this->buffer_num_tuples_ <<
" tuples output buffer ";
6501 out << this->grouping.schema() <<
print_info(this->grouping) <<
" (cumulative cost " << cost() <<
')';
6506 indent(out, level) <<
"probe input";
6507 probe.
print(out, level + 1);
6508 indent(out, level) <<
"build input";
6509 build.
print(out, level + 1);
6519template<
bool C,
bool PreOrder>
6523 template<
typename T>
using Const =
typename super::template Const<T>;
6524 using callback_t = std::conditional_t<C, ConstMatchBaseVisitor, MatchBaseVisitor>;
6527 callback_t &callback_;
6530 recursive_matchbase_visitor(callback_t &callback) : callback_(callback) { }
6532 using super::operator();
6533#define DECLARE(CLASS) \
6534 void operator()(Const<CLASS> &M) override { \
6535 if constexpr (PreOrder) try { callback_(M); } catch (visit_skip_subtree) { return; } \
6536 super::operator()(M); \
6537 if constexpr (not PreOrder) callback_(M); \
6548 recursive_matchbase_visitor<C,
true>{*
this}(M);
6554 recursive_matchbase_visitor<C,
false>{*
this}(M);
6562#define INSTANTIATE(CLASS) \
6563 template struct m::wasm::CLASS; \
6564 template struct m::Match<m::wasm::CLASS>;
__attribute__((constructor(202))) static void register_interpreter()
#define M_insist_no_ternary_logic()
#define FUNCTION(NAME, TYPE)
std::pair< const Constant &, bool > get_valid_bound(const ast::Expr &expr)
Given an Expr expr representing a valid bound, returns a pair consiting of a constant and a boolean f...
void index_scan_resolve_attribute_type(const Match< IndexScan< IndexMethod > > &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
Resolves the attribute type and calls the appropriate codegen function.
void index_scan_resolve_strategy(const Index &index, const index_scan_bounds_t &bounds, const Match< IndexScan< IndexMethod > > &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
Resolves the index scan strategy and calls the appropriate codegen function.
Ptr< void > get_array_index_base_address(const ThreadSafePooledString &table_name, const ThreadSafePooledString &attr_name)
Returns a pointer to the beginning of array index for table table_name and attribute attr_name.
std::pair< std::vector< Schema::Identifier >, std::vector< Schema::Identifier > > decompose_equi_predicate(const cnf::CNF &cnf, const Schema &schema_left)
Decompose the equi-predicate cnf, i.e.
#define RESOLVE_INDEX_METHOD(ATTRTYPE, SQLTYPE)
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.
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)
U64x1 get_num_rows(const ThreadSafePooledString &table_name)
Returns the number of rows of table table_name.
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)
void write_result_set(const Schema &schema, const DataLayoutFactory &factory, uint64_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.
#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.
uint64_t compute_initial_ht_capacity(const Operator &op, double load_factor)
Computes the initial hash table capacity for op.
U64x1 get_array_index_num_entries(const ThreadSafePooledString &table_name, const ThreadSafePooledString &attr_name)
Returns the number of entries of array index for table table_name and attribute attr_name.
#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.
#define M_unreachable(MSG)
#define M_CONSTEXPR_COND(COND, IF_TRUE, IF_FALSE)
std::ostream & indent(std::ostream &out, unsigned indentation)
Start a new line with proper indentation.
SoftPipelineBreakerStrategy
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 ...
const Schema & layout_schema
_I32x1 strcmp(NChar left, NChar right, bool reverse=false)
Compares two strings left and right.
_I32x1 strncmp(NChar left, NChar right, U32x1 len, bool reverse=false)
Compares two strings left and right.
bool can_be_null(const SQL_t &variant)
Bool< L > not_null(SQL_t &variant)
typename detail::_var_helper< T >::type _Var
Local variable that can always be NULL.
typename detail::var_helper< T >::type Var
Local variable.
auto make_signed()
Conversion of a PrimitiveExpr<T, L> to a PrimitiveExpr<std::make_signed_t<T>, L>.
void GOTO(const Block &block)
Jumps to the end of block.
and
Constructs a new PrimitiveExpr from a constant value.
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, U64x1 tuple_id)
Compiles the data layout layout starting at memory address base_address and containing tuples of sche...
typename detail::global_helper< T >::type Global
Global variable.
Bool< L > is_null(SQL_t &variant)
auto Select(C &&_cond, T &&_tru, U &&_fals)
auto max(PrimitiveExpr< U, L > other) -> PrimitiveExpr< common_type_t< T, U >, L > std
Computes the maximum of this and other.
void discard()
Discards this.
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< uint64_t, Kind, false > &tuple_id)
Compiles the data layout layout containing tuples of schema layout_schema such that it sequentially l...
PrimitiveExpr< uint64_t, L > L L L L U
for(std::size_t idx=1;idx< num_vectors;++idx) res.emplace((vectors_[idx].bitmask()<< uint32_t(idx *vector_type return * res
PrimitiveExpr< ResultType, ResultL > binary(::wasm::BinaryOp op, PrimitiveExpr< OperandType, OperandL > other)
Helper function to implement binary operations.
PrimitiveExpr clone() const
Creates and returns a deep copy of this.
and arithmetically_combinable< T, U, L > auto L auto L auto min(PrimitiveExpr< U, L > other) -> PrimitiveExpr< common_type_t< T, U >, L >
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...
std::function< void(void)> pipeline_t
bool streq(const char *first, const char *second)
bool strneq(const char *first, const char *second, std::size_t n)
bool M_EXPORT contains(const H &haystack, const N &needle)
Checks whether haystack contains needle.
and arithmetic< U > and same_signedness< T, U > U
bool M_EXPORT init(void)
Initializes the mu*t*able library.
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.
void register_wasm_operators(PhysicalOptimizer &phys_opt)
Registers physical Wasm operators in phys_opt depending on the set CLI options.
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.
The catalog contains all Databases and keeps track of all meta information of the database system.
Database & get_database_in_use()
Returns a reference to the Database that is currently in use, if any.
ThreadSafePooledString pool(const char *str) const
Creates an internalized copy of the string str by adding it to the internal StringPool.
static Catalog & Get()
Return a reference to the single Catalog instance.
m::ArgParser & arg_parser()
The type of character strings, both fixed length and varying length.
auto find(const Schema::Identifier &id) const
void merge(ConditionPropertyMap &other)
void add(Schema::Identifier id, Property P)
static ConditionSet Make_Unsatisfiable()
void add_condition(Cond &&cond)
void project_and_rename(const std::vector< std::pair< Schema::Identifier, Schema::Identifier > > &old2new)
void add_or_replace_condition(Cond &&cond)
ThreadSafePooledString name
the name of the database
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.
An Operator represents an operation in a query plan.
OperatorInformation & info()
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.
An Identifier is composed of a name and an optional prefix.
ThreadSafePooledString name
the name of this Identifier
static entry_type CreateArtificial()
@ NOT_NULLABLE
entry must not be NULL
A Schema represents a sequence of identifiers, optionally with a prefix, and their associated types.
std::size_t num_entries() const
Returns the number of entries in this Schema.
Schema deduplicate() const
Returns a deduplicated version of this Schema, i.e.
iterator find(const Identifier &id)
Returns an iterator to the entry with the given Identifier id, or end() if no such entry exists.
void add(entry_type e)
Adds the entry e to this Schema.
Schema drop_constants() const
Returns a copy of this Schema where all constant entries are removed.
bool has(const Identifier &id) const
Returns true iff this Schema contains an entry with Identifier id.
This class represents types in the SQL type system.
static Pooled< Numeric > Get_Double(category_t category)
Returns a Numeric type of given category for 64 bit floating-points.
static Pooled< Numeric > Get_Integer(category_t category, unsigned num_bytes)
Returns a Numeric type for integrals of given category and num_bytes bytes.
static WasmContext & Get_Wasm_Context_By_ID(unsigned id)
Returns a reference to the WasmContext with ID id.
A constant: a string literal or a numeric constant.
const Type * type() const
Returns the Type of this Expr.
virtual bool can_be_null() const =0
Returns true iff this Expr is nullable, i.e.
A query expression for nested queries.
static Token CreateArtificial(TokenType type=TK_EOF)
A CNF represents a conjunction of cnf::Clauses.
Schema get_required() const
Returns a Schema instance containing all required definitions (of Attributes and other Designators).
A cnf::Clause represents a disjunction of Predicates.
A Predicate contains a Expr of Boolean type in either positive or negative form.
A simple index based on a sorted array that maps keys to their tuple_id.
A recursive model index with two layers consiting only of linear monels that maps keys to their tuple...
Signals that an argument to a function of method was invalid.
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.
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.
Exception class which can be thrown to stop entire recursion in visitors.
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.
void attach_to_current()
Attaches this Block to the wasm::Block currently active in the Module.
Buffers tuples by materializing them into memory.
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...
U64x1 size() const
Returns the current size of the buffer.
Ptr< void > base_address() const
Returns the base address of the buffer.
void consume()
Emits code to store the current tuple into the buffer.
void setup()
Performs the setup of all local variables of this buffer (by reading them from the global backups iff...
void teardown()
Performs the teardown of all local variables of this buffer (by storing them into the global backups ...
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)
void inc_num_tuples(U64x1 n=U64x1(1))
Increments the number of result tuples produced by n.
uint64_t get_literal_raw_address(const char *literal) const
Returns the raw address at which literal is stored.
std::size_t num_simd_lanes() const
Returns the number of SIMD lanes used.
NChar get_literal_address(const char *literal) const
Returns the address at which literal is stored.
std::size_t num_simd_lanes_preferred() const
Returns the number of SIMD lanes preferred by other operators.
Environment & env()
Returns the current Environment.
void set_num_simd_lanes(std::size_t n)
Sets the number of SIMD lanes used to n.
void update_num_simd_lanes_preferred(std::size_t n)
Updates the number of SIMD lanes preferred by n.
void set_num_tuples(U64x1 n)
Set the number of result tuples produced to n.
U64x1 num_tuples() const
Returns the number of result tuples produced.
static CodeGenContext & Get()
Scope scoped_environment()
Creates a new, scoped Environment.
Binds Schema::Identifiers to Expr<T>s.
auto compile(T &&t) const
Compile t by delegating compilation to an ExprCompiler for this Environment.
void add_predicate(SQL_boolean_t &&pred)
Adds the predicate pred to the predication predicate.
void add(Schema::Identifier id, T &&expr)
Adds a mapping from id to expr.
SQL_t get(const Schema::Identifier &id) const
Returns the copied entry for identifier id.
SQL_boolean_t extract_predicate()
Returns the moved current predication predicate.
bool has(const Schema::Identifier &id) const
Returns true iff this Environment contains id.
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.
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.
value_t get(const Schema::Identifier &id) const
Returns the copied entry for identifier id.
value_t extract(const Schema::Identifier &id)
Returns the moved entry for identifier id.
Helper struct as proxy to access a single value (inclusive NULL bit) of a hash table entry.
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.
An abstract MatchBase for the WasmV8 backend.
PrimitiveExpr< T, L > get_global(const char *name)
static unsigned ID()
Returns the ID of the current module.
void emit_call(const char *fn, PrimitiveExpr< ParamTypes, ParamLs >... args)
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.
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.
friend std::ostream & operator<<(std::ostream &out, const print_info &info)