be/src/exec/common/join_op_utils.h
Line | Count | Source |
1 | | // Licensed to the Apache Software Foundation (ASF) under one |
2 | | // or more contributor license agreements. See the NOTICE file |
3 | | // distributed with this work for additional information |
4 | | // regarding copyright ownership. The ASF licenses this file |
5 | | // to you under the Apache License, Version 2.0 (the |
6 | | // "License"); you may not use this file except in compliance |
7 | | // with the License. You may obtain a copy of the License at |
8 | | // |
9 | | // http://www.apache.org/licenses/LICENSE-2.0 |
10 | | // |
11 | | // Unless required by applicable law or agreed to in writing, |
12 | | // software distributed under the License is distributed on an |
13 | | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
14 | | // KIND, either express or implied. See the License for the |
15 | | // specific language governing permissions and limitations |
16 | | // under the License. |
17 | | |
18 | | #pragma once |
19 | | |
20 | | // Lightweight join-op level types split out of join_utils.h so that |
21 | | // exec/pipeline/dependency.h (which holds JoinOpVariants / AsofIndexVariant |
22 | | // members by value) does not have to see the hash-table machinery. |
23 | | // Everything here depends only on thrift enums and std containers. |
24 | | |
25 | | #include <gen_cpp/PlanNodes_types.h> |
26 | | #include <pdqsort.h> |
27 | | |
28 | | #include <cstdint> |
29 | | #include <variant> |
30 | | #include <vector> |
31 | | |
32 | | #include "common/compiler_util.h" |
33 | | |
34 | | namespace doris { |
35 | | |
36 | | using JoinOpVariants = |
37 | | std::variant<std::integral_constant<TJoinOp::type, TJoinOp::INNER_JOIN>, |
38 | | std::integral_constant<TJoinOp::type, TJoinOp::LEFT_SEMI_JOIN>, |
39 | | std::integral_constant<TJoinOp::type, TJoinOp::LEFT_ANTI_JOIN>, |
40 | | std::integral_constant<TJoinOp::type, TJoinOp::LEFT_OUTER_JOIN>, |
41 | | std::integral_constant<TJoinOp::type, TJoinOp::FULL_OUTER_JOIN>, |
42 | | std::integral_constant<TJoinOp::type, TJoinOp::RIGHT_OUTER_JOIN>, |
43 | | std::integral_constant<TJoinOp::type, TJoinOp::CROSS_JOIN>, |
44 | | std::integral_constant<TJoinOp::type, TJoinOp::RIGHT_SEMI_JOIN>, |
45 | | std::integral_constant<TJoinOp::type, TJoinOp::RIGHT_ANTI_JOIN>, |
46 | | std::integral_constant<TJoinOp::type, TJoinOp::NULL_AWARE_LEFT_ANTI_JOIN>, |
47 | | std::integral_constant<TJoinOp::type, TJoinOp::NULL_AWARE_LEFT_SEMI_JOIN>, |
48 | | std::integral_constant<TJoinOp::type, TJoinOp::ASOF_LEFT_INNER_JOIN>, |
49 | | std::integral_constant<TJoinOp::type, TJoinOp::ASOF_LEFT_OUTER_JOIN>>; |
50 | | |
51 | 482k | inline bool is_asof_join(TJoinOp::type join_op) { |
52 | 482k | return join_op == TJoinOp::ASOF_LEFT_INNER_JOIN || join_op == TJoinOp::ASOF_LEFT_OUTER_JOIN; |
53 | 482k | } |
54 | | |
55 | | template <int JoinOpType> |
56 | | inline constexpr bool is_asof_join_op_v = |
57 | | JoinOpType == TJoinOp::ASOF_LEFT_INNER_JOIN || JoinOpType == TJoinOp::ASOF_LEFT_OUTER_JOIN; |
58 | | |
59 | | template <int JoinOpType> |
60 | | inline constexpr bool is_asof_outer_join_op_v = JoinOpType == TJoinOp::ASOF_LEFT_OUTER_JOIN; |
61 | | |
62 | | // ASOF JOIN index with inline values for cache-friendly branchless binary search. |
63 | | // IntType is the integer representation of the ASOF column value: |
64 | | // uint32_t for DateV2, uint64_t for DateTimeV2 and TimestampTZ. |
65 | | // Rows are sorted by asof_value during build, then materialized into SoA arrays |
66 | | // so probe-side binary search only touches the ASOF values hot path. |
67 | | template <typename IntType> |
68 | | struct AsofIndexGroup { |
69 | | using int_type = IntType; |
70 | | |
71 | | struct Entry { |
72 | | IntType asof_value; |
73 | | uint32_t row_index; // 1-based, 0 = invalid/padding |
74 | | }; |
75 | | |
76 | | std::vector<Entry> entries; |
77 | | std::vector<IntType> asof_values; |
78 | | std::vector<uint32_t> row_indexes; |
79 | | |
80 | 6.94k | void add_row(IntType value, uint32_t row_idx) { entries.push_back({value, row_idx}); }_ZN5doris14AsofIndexGroupIjE7add_rowEjj Line | Count | Source | 80 | 1.06k | void add_row(IntType value, uint32_t row_idx) { entries.push_back({value, row_idx}); } |
_ZN5doris14AsofIndexGroupImE7add_rowEmj Line | Count | Source | 80 | 5.87k | void add_row(IntType value, uint32_t row_idx) { entries.push_back({value, row_idx}); } |
|
81 | | |
82 | 883 | void sort_and_finalize() { |
83 | 883 | if (entries.empty()) { |
84 | 4 | return; |
85 | 4 | } |
86 | 879 | if (entries.size() > 1) { |
87 | 729 | pdqsort(entries.begin(), entries.end(), |
88 | 26.6k | [](const Entry& a, const Entry& b) { return a.asof_value < b.asof_value; });_ZZN5doris14AsofIndexGroupIjE17sort_and_finalizeEvENKUlRKNS1_5EntryES4_E_clES4_S4_ Line | Count | Source | 88 | 2.06k | [](const Entry& a, const Entry& b) { return a.asof_value < b.asof_value; }); |
_ZZN5doris14AsofIndexGroupImE17sort_and_finalizeEvENKUlRKNS1_5EntryES4_E_clES4_S4_ Line | Count | Source | 88 | 24.5k | [](const Entry& a, const Entry& b) { return a.asof_value < b.asof_value; }); |
|
89 | 729 | } |
90 | | |
91 | 879 | asof_values.resize(entries.size()); |
92 | 879 | row_indexes.resize(entries.size()); |
93 | 7.82k | for (size_t i = 0; i < entries.size(); ++i) { |
94 | 6.94k | asof_values[i] = entries[i].asof_value; |
95 | 6.94k | row_indexes[i] = entries[i].row_index; |
96 | 6.94k | } |
97 | | |
98 | 879 | std::vector<Entry>().swap(entries); |
99 | 879 | } _ZN5doris14AsofIndexGroupIjE17sort_and_finalizeEv Line | Count | Source | 82 | 27 | void sort_and_finalize() { | 83 | 27 | if (entries.empty()) { | 84 | 4 | return; | 85 | 4 | } | 86 | 23 | if (entries.size() > 1) { | 87 | 16 | pdqsort(entries.begin(), entries.end(), | 88 | 16 | [](const Entry& a, const Entry& b) { return a.asof_value < b.asof_value; }); | 89 | 16 | } | 90 | | | 91 | 23 | asof_values.resize(entries.size()); | 92 | 23 | row_indexes.resize(entries.size()); | 93 | 1.09k | for (size_t i = 0; i < entries.size(); ++i) { | 94 | 1.06k | asof_values[i] = entries[i].asof_value; | 95 | 1.06k | row_indexes[i] = entries[i].row_index; | 96 | 1.06k | } | 97 | | | 98 | 23 | std::vector<Entry>().swap(entries); | 99 | 23 | } |
_ZN5doris14AsofIndexGroupImE17sort_and_finalizeEv Line | Count | Source | 82 | 856 | void sort_and_finalize() { | 83 | 856 | if (entries.empty()) { | 84 | 0 | return; | 85 | 0 | } | 86 | 856 | if (entries.size() > 1) { | 87 | 713 | pdqsort(entries.begin(), entries.end(), | 88 | 713 | [](const Entry& a, const Entry& b) { return a.asof_value < b.asof_value; }); | 89 | 713 | } | 90 | | | 91 | 856 | asof_values.resize(entries.size()); | 92 | 856 | row_indexes.resize(entries.size()); | 93 | 6.73k | for (size_t i = 0; i < entries.size(); ++i) { | 94 | 5.87k | asof_values[i] = entries[i].asof_value; | 95 | 5.87k | row_indexes[i] = entries[i].row_index; | 96 | 5.87k | } | 97 | | | 98 | 856 | std::vector<Entry>().swap(entries); | 99 | 856 | } |
|
100 | | |
101 | 817 | const IntType* values_data() const { return asof_values.data(); }_ZNK5doris14AsofIndexGroupIjE11values_dataEv Line | Count | Source | 101 | 1 | const IntType* values_data() const { return asof_values.data(); } |
_ZNK5doris14AsofIndexGroupImE11values_dataEv Line | Count | Source | 101 | 816 | const IntType* values_data() const { return asof_values.data(); } |
|
102 | | |
103 | | // Branchless lower_bound: first i where asof_values[i] >= target |
104 | 454 | ALWAYS_INLINE size_t lower_bound(IntType target) const { |
105 | 454 | size_t lo = 0, n = asof_values.size(); |
106 | 2.30k | while (n > 1) { |
107 | 1.85k | size_t half = n / 2; |
108 | 1.85k | lo += half * (asof_values[lo + half] < target); |
109 | 1.85k | n -= half; |
110 | 1.85k | } |
111 | 454 | if (lo < asof_values.size()) { |
112 | 453 | lo += (asof_values[lo] < target); |
113 | 453 | } |
114 | 454 | return lo; |
115 | 454 | } _ZNK5doris14AsofIndexGroupIjE11lower_boundEj Line | Count | Source | 104 | 33 | ALWAYS_INLINE size_t lower_bound(IntType target) const { | 105 | 33 | size_t lo = 0, n = asof_values.size(); | 106 | 163 | while (n > 1) { | 107 | 130 | size_t half = n / 2; | 108 | 130 | lo += half * (asof_values[lo + half] < target); | 109 | 130 | n -= half; | 110 | 130 | } | 111 | 33 | if (lo < asof_values.size()) { | 112 | 32 | lo += (asof_values[lo] < target); | 113 | 32 | } | 114 | 33 | return lo; | 115 | 33 | } |
_ZNK5doris14AsofIndexGroupImE11lower_boundEm Line | Count | Source | 104 | 421 | ALWAYS_INLINE size_t lower_bound(IntType target) const { | 105 | 421 | size_t lo = 0, n = asof_values.size(); | 106 | 2.14k | while (n > 1) { | 107 | 1.72k | size_t half = n / 2; | 108 | 1.72k | lo += half * (asof_values[lo + half] < target); | 109 | 1.72k | n -= half; | 110 | 1.72k | } | 111 | 421 | if (lo < asof_values.size()) { | 112 | 421 | lo += (asof_values[lo] < target); | 113 | 421 | } | 114 | 421 | return lo; | 115 | 421 | } |
|
116 | | |
117 | | // Branchless upper_bound: first i where asof_values[i] > target |
118 | 947 | ALWAYS_INLINE size_t upper_bound(IntType target) const { |
119 | 947 | size_t lo = 0, n = asof_values.size(); |
120 | 4.06k | while (n > 1) { |
121 | 3.12k | size_t half = n / 2; |
122 | 3.12k | lo += half * (asof_values[lo + half] <= target); |
123 | 3.12k | n -= half; |
124 | 3.12k | } |
125 | 947 | if (lo < asof_values.size()) { |
126 | 946 | lo += (asof_values[lo] <= target); |
127 | 946 | } |
128 | 947 | return lo; |
129 | 947 | } _ZNK5doris14AsofIndexGroupIjE11upper_boundEj Line | Count | Source | 118 | 41 | ALWAYS_INLINE size_t upper_bound(IntType target) const { | 119 | 41 | size_t lo = 0, n = asof_values.size(); | 120 | 215 | while (n > 1) { | 121 | 174 | size_t half = n / 2; | 122 | 174 | lo += half * (asof_values[lo + half] <= target); | 123 | 174 | n -= half; | 124 | 174 | } | 125 | 41 | if (lo < asof_values.size()) { | 126 | 40 | lo += (asof_values[lo] <= target); | 127 | 40 | } | 128 | 41 | return lo; | 129 | 41 | } |
_ZNK5doris14AsofIndexGroupImE11upper_boundEm Line | Count | Source | 118 | 906 | ALWAYS_INLINE size_t upper_bound(IntType target) const { | 119 | 906 | size_t lo = 0, n = asof_values.size(); | 120 | 3.85k | while (n > 1) { | 121 | 2.94k | size_t half = n / 2; | 122 | 2.94k | lo += half * (asof_values[lo + half] <= target); | 123 | 2.94k | n -= half; | 124 | 2.94k | } | 125 | 906 | if (lo < asof_values.size()) { | 126 | 906 | lo += (asof_values[lo] <= target); | 127 | 906 | } | 128 | 906 | return lo; | 129 | 906 | } |
|
130 | | |
131 | | // Semantics by (is_greater, is_strict): |
132 | | // (true, false): probe >= build -> find largest build value <= probe |
133 | | // (true, true): probe > build -> find largest build value < probe |
134 | | // (false, false): probe <= build -> find smallest build value >= probe |
135 | | // (false, true): probe < build -> find smallest build value > probe |
136 | | // Returns the build row index of the best match, or 0 if no match. |
137 | | template <bool IsGreater, bool IsStrict> |
138 | 1.38k | ALWAYS_INLINE uint32_t find_best_match(IntType probe_value) const { |
139 | 1.38k | if (asof_values.empty()) { |
140 | 4 | return 0; |
141 | 4 | } |
142 | 1.38k | if constexpr (IsGreater) { |
143 | 944 | size_t pos = IsStrict ? lower_bound(probe_value) : upper_bound(probe_value); |
144 | 944 | return pos > 0 ? row_indexes[pos - 1] : 0; |
145 | 944 | } else { |
146 | 439 | size_t pos = IsStrict ? upper_bound(probe_value) : lower_bound(probe_value); |
147 | 439 | return pos < asof_values.size() ? row_indexes[pos] : 0; |
148 | 439 | } |
149 | 1.38k | } _ZNK5doris14AsofIndexGroupIjE15find_best_matchILb1ELb1EEEjj Line | Count | Source | 138 | 15 | ALWAYS_INLINE uint32_t find_best_match(IntType probe_value) const { | 139 | 15 | if (asof_values.empty()) { | 140 | 1 | return 0; | 141 | 1 | } | 142 | 14 | if constexpr (IsGreater) { | 143 | 14 | size_t pos = IsStrict ? lower_bound(probe_value) : upper_bound(probe_value); | 144 | 14 | return pos > 0 ? row_indexes[pos - 1] : 0; | 145 | | } else { | 146 | | size_t pos = IsStrict ? upper_bound(probe_value) : lower_bound(probe_value); | 147 | | return pos < asof_values.size() ? row_indexes[pos] : 0; | 148 | | } | 149 | 14 | } |
_ZNK5doris14AsofIndexGroupIjE15find_best_matchILb1ELb0EEEjj Line | Count | Source | 138 | 22 | ALWAYS_INLINE uint32_t find_best_match(IntType probe_value) const { | 139 | 22 | if (asof_values.empty()) { | 140 | 1 | return 0; | 141 | 1 | } | 142 | 21 | if constexpr (IsGreater) { | 143 | 21 | size_t pos = IsStrict ? lower_bound(probe_value) : upper_bound(probe_value); | 144 | 21 | return pos > 0 ? row_indexes[pos - 1] : 0; | 145 | | } else { | 146 | | size_t pos = IsStrict ? upper_bound(probe_value) : lower_bound(probe_value); | 147 | | return pos < asof_values.size() ? row_indexes[pos] : 0; | 148 | | } | 149 | 21 | } |
_ZNK5doris14AsofIndexGroupIjE15find_best_matchILb0ELb1EEEjj Line | Count | Source | 138 | 16 | ALWAYS_INLINE uint32_t find_best_match(IntType probe_value) const { | 139 | 16 | if (asof_values.empty()) { | 140 | 1 | return 0; | 141 | 1 | } | 142 | | if constexpr (IsGreater) { | 143 | | size_t pos = IsStrict ? lower_bound(probe_value) : upper_bound(probe_value); | 144 | | return pos > 0 ? row_indexes[pos - 1] : 0; | 145 | 15 | } else { | 146 | 15 | size_t pos = IsStrict ? upper_bound(probe_value) : lower_bound(probe_value); | 147 | 15 | return pos < asof_values.size() ? row_indexes[pos] : 0; | 148 | 15 | } | 149 | 15 | } |
_ZNK5doris14AsofIndexGroupIjE15find_best_matchILb0ELb0EEEjj Line | Count | Source | 138 | 15 | ALWAYS_INLINE uint32_t find_best_match(IntType probe_value) const { | 139 | 15 | if (asof_values.empty()) { | 140 | 1 | return 0; | 141 | 1 | } | 142 | | if constexpr (IsGreater) { | 143 | | size_t pos = IsStrict ? lower_bound(probe_value) : upper_bound(probe_value); | 144 | | return pos > 0 ? row_indexes[pos - 1] : 0; | 145 | 14 | } else { | 146 | 14 | size_t pos = IsStrict ? upper_bound(probe_value) : lower_bound(probe_value); | 147 | 14 | return pos < asof_values.size() ? row_indexes[pos] : 0; | 148 | 14 | } | 149 | 14 | } |
_ZNK5doris14AsofIndexGroupImE15find_best_matchILb1ELb1EEEjm Line | Count | Source | 138 | 205 | ALWAYS_INLINE uint32_t find_best_match(IntType probe_value) const { | 139 | 205 | if (asof_values.empty()) { | 140 | 0 | return 0; | 141 | 0 | } | 142 | 205 | if constexpr (IsGreater) { | 143 | 205 | size_t pos = IsStrict ? lower_bound(probe_value) : upper_bound(probe_value); | 144 | 205 | return pos > 0 ? row_indexes[pos - 1] : 0; | 145 | | } else { | 146 | | size_t pos = IsStrict ? upper_bound(probe_value) : lower_bound(probe_value); | 147 | | return pos < asof_values.size() ? row_indexes[pos] : 0; | 148 | | } | 149 | 205 | } |
_ZNK5doris14AsofIndexGroupImE15find_best_matchILb1ELb0EEEjm Line | Count | Source | 138 | 704 | ALWAYS_INLINE uint32_t find_best_match(IntType probe_value) const { | 139 | 704 | if (asof_values.empty()) { | 140 | 0 | return 0; | 141 | 0 | } | 142 | 704 | if constexpr (IsGreater) { | 143 | 704 | size_t pos = IsStrict ? lower_bound(probe_value) : upper_bound(probe_value); | 144 | 704 | return pos > 0 ? row_indexes[pos - 1] : 0; | 145 | | } else { | 146 | | size_t pos = IsStrict ? upper_bound(probe_value) : lower_bound(probe_value); | 147 | | return pos < asof_values.size() ? row_indexes[pos] : 0; | 148 | | } | 149 | 704 | } |
_ZNK5doris14AsofIndexGroupImE15find_best_matchILb0ELb1EEEjm Line | Count | Source | 138 | 198 | ALWAYS_INLINE uint32_t find_best_match(IntType probe_value) const { | 139 | 198 | if (asof_values.empty()) { | 140 | 0 | return 0; | 141 | 0 | } | 142 | | if constexpr (IsGreater) { | 143 | | size_t pos = IsStrict ? lower_bound(probe_value) : upper_bound(probe_value); | 144 | | return pos > 0 ? row_indexes[pos - 1] : 0; | 145 | 198 | } else { | 146 | 198 | size_t pos = IsStrict ? upper_bound(probe_value) : lower_bound(probe_value); | 147 | 198 | return pos < asof_values.size() ? row_indexes[pos] : 0; | 148 | 198 | } | 149 | 198 | } |
_ZNK5doris14AsofIndexGroupImE15find_best_matchILb0ELb0EEEjm Line | Count | Source | 138 | 212 | ALWAYS_INLINE uint32_t find_best_match(IntType probe_value) const { | 139 | 212 | if (asof_values.empty()) { | 140 | 0 | return 0; | 141 | 0 | } | 142 | | if constexpr (IsGreater) { | 143 | | size_t pos = IsStrict ? lower_bound(probe_value) : upper_bound(probe_value); | 144 | | return pos > 0 ? row_indexes[pos - 1] : 0; | 145 | 212 | } else { | 146 | 212 | size_t pos = IsStrict ? upper_bound(probe_value) : lower_bound(probe_value); | 147 | 212 | return pos < asof_values.size() ? row_indexes[pos] : 0; | 148 | 212 | } | 149 | 212 | } |
|
150 | | }; |
151 | | |
152 | | // Type-erased container for all ASOF index groups. |
153 | | // DateV2 -> uint32_t, DateTimeV2/TimestampTZ -> uint64_t. |
154 | | using AsofIndexVariant = std::variant<std::monostate, std::vector<AsofIndexGroup<uint32_t>>, |
155 | | std::vector<AsofIndexGroup<uint64_t>>>; |
156 | | |
157 | | } // namespace doris |