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         [](uint32_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<U32x1>> 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<uint32_t>();
 
  680                        IF (*counter == window_size) {
 
  688                        counter_backup = *counter;
 
  702            FUNCTION(child_pipeline, 
void(
void))
 
  706                std::optional<Var<U32x1>> num_tuples; 
 
  716                                     "SIMDfication with predication not supported");
 
  717                            *num_tuples += env.extract_predicate<_Boolx1>().is_true_and_not_null().to<uint32_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 std::in_range<uint32_t>(initial_capacity) ? initial_capacity : std::numeric_limits<uint32_t>::max();
 
 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<U32x1>> num_tuples; 
 
 1137                        *num_tuples += env.extract_predicate<_Boolx1>().is_true_and_not_null().to<uint32_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 uint32_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(U32x1(*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                                      : U32x1(index.num_entries()));
 
 1604        M_insist(std::in_range<uint32_t>(M.batch_size), 
"should fit in uint32_t");
 
 1609            U32x1 num_results = hi - lo;
 
 1610            U32x1 num_results_cpy = num_results.clone();
 
 1611            U32x1 batch_size = M.batch_size == 0 ? num_results.clone() : U32x1(M.batch_size);
 
 1612            U32x1 batch_size_cpy = batch_size.clone();
 
 1613            return Select(batch_size < num_results, batch_size_cpy, num_results_cpy);
 
 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 > U32x1(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(U32x1(*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 int32_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 += int32_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        uint32_t num_results = hi - lo;
 
 1830        uint32_t *buffer_address = 
Module::Allocator().raw_malloc<uint32_t>(num_results + 1); 
 
 1833        uint32_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<uint32_t>(it->second), 
"tuple id must fit in uint32_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(uint32_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<uint32_t>(it->second), 
"tuple id must fit in uint32_t");
 
 1893            index_scan_parent_pipeline(uint32_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 uint32_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<uint32_t>(lo), 
"should fit in uint32_t");
 
 1986    M_insist(std::in_range<uint32_t>(hi), 
"should fit in uint32_t");
 
 1989    std::optional<U32x1> begin;
 
 1990    std::optional<U32x1> end;
 
 1999        offset_address[0] = uint32_t(lo);
 
 2000        offset_address[1] = uint32_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<uint32_t>(M.batch_size), 
"should fit in uint32_t");
 
 2017            U32x1 num_results = end->clone() - begin->clone();
 
 2018            U32x1 num_results_cpy = num_results.clone();
 
 2019            U32x1 batch_size = M.batch_size == 0 ? num_results.clone() : U32x1(M.batch_size);
 
 2020            U32x1 batch_size_cpy = batch_size.clone();
 
 2021            return Select(batch_size < num_results, batch_size_cpy, num_results_cpy);
 
 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 > U32x1(0)) { 
 
 2076            constexpr int32_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 += int32_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    U32x1 size_parent = needs_buffer_parent ? buffer_parent->size()
 
 5427                                            : 
get_num_rows(as<const ScanOperator>(M.parent).store().table().name());
 
 5428    U32x1 size_child = needs_buffer_child ? buffer_child->size()
 
 5429                                          : 
get_num_rows(as<const ScanOperator>(M.child).store().table().name());
 
 5430    WHILE (tuple_id_parent < size_parent 
and tuple_id_child < size_child) { 
 
 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<U32x1>> 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 uint32_t limit = M.limit.offset() + M.limit.limit();
 
 5489            IF (*counter >= limit) {
 
 5490                GOTO(*teardown_block);
 
 5494            if (M.limit.offset()) {
 
 5495                IF (*counter >= uint32_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.
uint32_t compute_initial_ht_capacity(const Operator &op, double load_factor)
Computes the initial hash table capacity for op.
#define RESOLVE_INDEX_METHOD(ATTRTYPE, SQLTYPE)
void write_result_set(const Schema &schema, const DataLayoutFactory &factory, uint32_t window_size, const m::wasm::MatchBase &child)
Emits code to write the result set of the Schema schema using the DataLayout created by factory.
U32x1 get_array_index_num_entries(const ThreadSafePooledString &table_name, const ThreadSafePooledString &attr_name)
Returns the number of entries of array index for table table_name and attribute attr_name.
void index_scan_codegen_compilation(const Index &index, const index_scan_bounds_t &bounds, const Match< IndexScan< IndexMethod > > &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
bool is_valid_bound(const ast::Expr &expr)
Returns true iff expr is a valid bound.
void index_scan_resolve_index_method(const index_scan_bounds_t &bounds, const Match< IndexScan< IndexMethod > > &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
Resolves the index method and calls the appropriate codegen function.
#define INSTANTIATE(CLASS)
Ptr< void > get_base_address(const ThreadSafePooledString &table_name)
Returns a pointer to the beginning of table table_name in the WebAssembly linear memory.
U32x1 get_num_rows(const ThreadSafePooledString &table_name)
Returns the number of rows of table table_name.
std::pair< std::vector< aggregate_info_t >, std::unordered_map< Schema::Identifier, avg_aggregate_info_t > > compute_aggregate_info(const std::vector< std::reference_wrapper< const FnApplicationExpr > > &aggregates, const Schema &schema, std::size_t aggregates_offset=0)
Computes and returns information about the aggregates aggregates which are contained in the schema sc...
void index_scan_codegen_hybrid(const Index &index, const index_scan_bounds_t &bounds, const Match< IndexScan< IndexMethod > > &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
void index_scan_codegen_interpretation(const Index &index, const index_scan_bounds_t &bounds, const Match< IndexScan< IndexMethod > > &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
#define RESOLVE_KEYTYPE(INDEX)
index_scan_bounds_t extract_index_scan_bounds(const cnf::CNF &cnf)
Extracts the bounds for performing index scan from CNF cnf.
#define M_WASM_OPERATOR_LIST_TEMPLATED(X)
#define M_WASM_MATCH_LIST(X)
void add(const char *group_name, const char *short_name, const char *long_name, const char *description, Callback &&callback)
Adds a new group option to the ArgParser.
#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.
void compile_load_point_access(const Schema &tuple_value_schema, const Schema &tuple_addr_schema, Ptr< void > base_address, const storage::DataLayout &layout, const Schema &layout_schema, U32x1 tuple_id)
Compiles the data layout layout starting at memory address base_address and containing tuples of sche...
and
Constructs a new PrimitiveExpr from a constant value.
typename detail::global_helper< T >::type Global
Global variable.
Bool< L > is_null(SQL_t &variant)
auto Select(C &&_cond, T &&_tru, U &&_fals)
std::tuple< Block, Block, Block > compile_load_sequential(const Schema &tuple_value_schema, const Schema &tuple_addr_schema, Ptr< void > base_address, const storage::DataLayout &layout, std::size_t num_simd_lanes, const Schema &layout_schema, Variable< uint32_t, Kind, false > &tuple_id)
Compiles the data layout layout containing tuples of schema layout_schema such that it sequentially l...
auto max(PrimitiveExpr< U, L > other) -> PrimitiveExpr< common_type_t< T, U >, L > std
Computes the maximum of this and other.
void discard()
Discards this.
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...
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 ...
U32x1 size() const
Returns the current size of the buffer.
static ConditionSet pre_condition(std::size_t child_idx, const std::tuple< const CallbackOperator * > &partial_inner_nodes)
static void execute(const Match< Callback > &M, setup_t setup, pipeline_t pipeline, teardown_t teardown)
uint32_t get_literal_raw_address(const char *literal) const
Returns the raw address at which literal is stored.
void inc_num_tuples(U32x1 n=U32x1(1))
Increments the number of result tuples produced by n.
U32x1 num_tuples() const
Returns the number of result tuples produced.
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(U32x1 n)
Set the number of result tuples produced to n.
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)