11#pragma once
22#include " dq_hash_join_table.h"
33#include < vector>
4+ #include < ydb/library/yql/dq/comp_nodes/hash_join_utils/block_layout_converter.h>
45#include < yql/essentials/minikql/computation/mkql_block_reader.h>
56#include < yql/essentials/minikql/computation/mkql_computation_node.h>
67#include < yql/essentials/minikql/computation/mkql_computation_node_holders.h>
@@ -12,6 +13,46 @@ struct TColumnsMetadata {
1213 std::vector<TType*> ColumnTypes;
1314};
1415
16+ enum class ESide { Probe, Build };
17+
18+ template <typename T> struct TSides {
19+ T Build;
20+ T Probe;
21+
22+ T& SelectSide (ESide side) {
23+ return side == ESide::Build ? Build : Probe;
24+ }
25+
26+ const T& SelectSide (ESide side) const {
27+ return side == ESide::Build ? Build : Probe;
28+ }
29+ };
30+
31+
32+ /*
33+ usage:
34+ instead of copy pasting code and changing "build" to "probe", use TSides and ForEachSide to call same code twice.
35+ example:
36+
37+ void f(int buildSize, int probeSize, bool buildRequired, bool probeRequired){
38+ use(buildSize + (buildRequired ? 0 : transform(buildSize)));
39+ use(probeSize + (probeRequired ? 0 : transform(probeSize)));
40+ }
41+
42+ vs
43+
44+ void f(TSides<int> sizes, TSides<bool> required) {
45+ ForEachSide([&](ESide side){
46+ use(sizes.SelectSide(side) + (required.SelectSide(side) ? 0 : transform(sizes.SelectSide(side))));
47+ });
48+ }
49+
50+ */
51+ void ForEachSide (std::invocable<ESide> auto fn) {
52+ fn (ESide::Build);
53+ fn (ESide::Probe);
54+ }
55+
1556struct TJoinMetadata {
1657 TColumnsMetadata Build;
1758 TColumnsMetadata Probe;
@@ -21,7 +62,7 @@ struct TJoinMetadata {
2162TKeyTypes KeyTypesFromColumns (const std::vector<TType*>& types, const std::vector<ui32>& keyIndexes);
2263
2364template <EJoinKind Kind> struct TRenamedOutput {
24- TRenamedOutput (TDqRenames renames, const std::vector<TType*>& leftColumnTypes,
65+ TRenamedOutput (TDqUserRenames renames, const std::vector<TType*>& leftColumnTypes,
2566 const std::vector<TType*>& rightColumnTypes)
2667 : OutputBuffer()
2768 , NullTuples(std::max(leftColumnTypes.size(), rightColumnTypes.size()), NYql::NUdf::TUnboxedValuePod{})
@@ -46,13 +87,6 @@ template <EJoinKind Kind> struct TRenamedOutput {
4687 MKQL_ENSURE (tuple != nullptr , " null output row in semi/only join?" );
4788 for (int index = 0 ; index < std::ssize (Renames); ++index) {
4889 auto thisRename = Renames[index];
49- if constexpr (Kind == EJoinKind::LeftOnly || Kind == EJoinKind::LeftSemi) {
50- MKQL_ENSURE (thisRename.Side == JoinSide::kLeft ,
51- " rename has right column for LeftOnly or LeftSemi join??" );
52- } else {
53- MKQL_ENSURE (thisRename.Side == JoinSide::kRight ,
54- " rename has left column for RightOnly or RightSemi join??" );
55- }
5690 OutputBuffer.push_back (tuple[thisRename.Index ]);
5791 }
5892 };
@@ -67,7 +101,7 @@ template <EJoinKind Kind> struct TRenamedOutput {
67101 }
68102 for (int index = 0 ; index < std::ssize (Renames); ++index) {
69103 auto thisRename = Renames[index];
70- if (thisRename.Side == JoinSide ::kLeft ) {
104+ if (thisRename.Side == EJoinSide ::kLeft ) {
71105 OutputBuffer.push_back (probe[thisRename.Index ]);
72106 } else {
73107 OutputBuffer.push_back (build[thisRename.Index ]);
@@ -80,13 +114,14 @@ template <EJoinKind Kind> struct TRenamedOutput {
80114
81115 private:
82116 const std::vector<NYql::NUdf::TUnboxedValue> NullTuples;
83- const TDqRenames Renames;
117+ const TDqUserRenames Renames;
84118};
85119
86120// Some joins produce concatenation of 2 tuples, some produce one tuple(effectively)
87- template <typename Fun>
88- concept JoinMatchFun =
89- std::invocable<Fun, NJoinTable::TTuple> || std::invocable<Fun, NJoinTable::TTuple, NJoinTable::TTuple>;
121+ template <typename Fun, typename Tuple>
122+ concept JoinMatchFun = std::invocable<Fun, NJoinTable::TTuple> || std::invocable<Fun, TSides<Tuple>>;
123+
124+ IBlockLayoutConverter::TPackResult Flatten (std::vector<IBlockLayoutConverter::TPackResult> tuples);
90125
91126template <typename Source, EJoinKind Kind> class TJoin : public TComputationValue <TJoin<Source, Kind>> {
92127 using TBase = TComputationValue<TJoin>;
@@ -120,7 +155,7 @@ template <typename Source, EJoinKind Kind> class TJoin : public TComputationValu
120155 return Build_.UserDataSize ();
121156 }
122157
123- EFetchResult MatchRows (TComputationContext& ctx, JoinMatchFun auto consumeOneOrTwoTuples) {
158+ EFetchResult MatchRows (TComputationContext& ctx, auto consumeOneOrTwoTuples) {
124159 while (!Build_.Finished ()) {
125160 auto res = Build_.ForEachRow (ctx, [&](auto tuple) { Table_.Add ({tuple, tuple + Build_.UserDataSize ()}); });
126161 switch (res) {
@@ -163,27 +198,32 @@ template <typename Source, EJoinKind Kind> class TJoin : public TComputationValu
163198 });
164199 switch (result) {
165200 case NYql::NUdf::EFetchStatus::Finish: {
201+ int consumedTotal = 0 ;
166202 if (Table_.UnusedTrackingOn ()) {
167203 if constexpr (Kind == EJoinKind::RightSemi) {
168204 for (auto & v : Table_.MapView ()) {
169205 if (v.second .Used ) {
170206 for (NJoinTable::TTuple used : v.second .Tuples ) {
207+
208+ ++consumedTotal;
171209 consumeOneOrTwoTuples (used);
172210 }
173211 }
174212 }
175213 }
176214 Table_.ForEachUnused ([&](NJoinTable::TTuple unused) {
177215 if constexpr (Kind == EJoinKind::RightOnly) {
216+ ++consumedTotal;
178217 consumeOneOrTwoTuples (unused);
179218 }
180219 if constexpr (Kind == EJoinKind::Exclusion || Kind == EJoinKind::Right ||
181220 Kind == EJoinKind::Full) {
221+ ++consumedTotal;
182222 consumeOneOrTwoTuples (nullptr , unused);
183223 }
184224 });
185225 }
186- return EFetchResult::One;
226+ return consumedTotal == 0 ? EFetchResult::Finish : EFetchResult::One;
187227 }
188228 case NYql::NUdf::EFetchStatus::Yield: {
189229 return EFetchResult::Yield;
@@ -208,4 +248,99 @@ template <typename Source, EJoinKind Kind> class TJoin : public TComputationValu
208248 NJoinTable::TStdJoinTable Table_;
209249};
210250
251+ template <typename Source> class TJoinPackedTuples {
252+ public:
253+ using TTable = NJoinTable::TNeumannJoinTable;
254+
255+ TJoinPackedTuples (TSides<Source> sources, NUdf::TLoggerPtr logger, TString componentName,
256+ TSides<const NPackedTuple::TTupleLayout*> layouts)
257+ : Logger_(logger)
258+ , LogComponent_(logger->RegisterComponent (componentName))
259+ , Sources_(std::move(sources))
260+ , Layouts_(layouts)
261+ , Table_(Layouts_.Build)
262+ {}
263+
264+ IBlockLayoutConverter::TPackResult Flatten (std::vector<IBlockLayoutConverter::TPackResult> tuples) {
265+ IBlockLayoutConverter::TPackResult flattened;
266+ flattened.NTuples = std::accumulate (tuples.begin (), tuples.end (), i64 {0 },
267+ [](i64 summ, const auto & packRes) { return summ += packRes.NTuples ; });
268+
269+ i64 totalTuplesSize = std::accumulate (tuples.begin (), tuples.end (), i64 {0 }, [](i64 summ, const auto & packRes) {
270+ return summ += std::ssize (packRes.PackedTuples );
271+ });
272+ flattened.PackedTuples .reserve (totalTuplesSize);
273+
274+ i64 totaOverflowlSize =
275+ std::accumulate (tuples.begin (), tuples.end (), i64 {0 },
276+ [](i64 summ, const auto & packRes) { return summ += std::ssize (packRes.Overflow ); });
277+ flattened.Overflow .reserve (totaOverflowlSize);
278+
279+ int tupleSize = Layouts_.Build ->TotalRowSize ;
280+ for (const IBlockLayoutConverter::TPackResult& tupleBatch : tuples) {
281+ Layouts_.Build ->Concat (flattened.PackedTuples , flattened.Overflow ,
282+ std::ssize (flattened.PackedTuples ) / tupleSize, tupleBatch.PackedTuples .data (),
283+ tupleBatch.Overflow .data (), tupleBatch.PackedTuples .size () / tupleSize,
284+ tupleBatch.Overflow .size ());
285+ }
286+ return flattened;
287+ }
288+
289+ EFetchResult MatchRows ([[maybe_unused]] TComputationContext& ctx,
290+ JoinMatchFun<TTable::Tuple> auto consumeOneOrTwoTuples) {
291+ while (!Sources_.Build .Finished ()) {
292+ FetchResult<IBlockLayoutConverter::TPackResult> var = Sources_.Build .FetchRow ();
293+ switch (AsStatus (var)) {
294+ case NYql::NUdf::EFetchStatus::Finish: {
295+ Table_.BuildWith (Flatten (BuildChunks_));
296+ break ;
297+ }
298+ case NYql::NUdf::EFetchStatus::Yield: {
299+ return EFetchResult::Yield;
300+ }
301+ case NYql::NUdf::EFetchStatus::Ok: {
302+ auto & packResult = std::get<One<IBlockLayoutConverter::TPackResult>>(var);
303+ BuildChunks_.push_back (std::move (packResult.Data ));
304+ break ;
305+ }
306+ default :
307+ MKQL_ENSURE (false , " unreachable" );
308+ }
309+ }
310+ if (Table_.Empty ()) {
311+ return EFetchResult::Finish; // is it ok?
312+ }
313+
314+ if (!Sources_.Probe .Finished ()) {
315+ const FetchResult<IBlockLayoutConverter::TPackResult> var = Sources_.Probe .FetchRow ();
316+ const NKikimr::NMiniKQL::EFetchResult resEnum = AsResult (var);
317+
318+ if (resEnum == EFetchResult::One) {
319+ const IBlockLayoutConverter::TPackResult& thisPackResult =
320+ std::get<One<IBlockLayoutConverter::TPackResult>>(var).Data ;
321+ for (int index = 0 ; index < thisPackResult.NTuples ; ++index) {
322+ const ui8* thisRow = &thisPackResult.PackedTuples [index * Layouts_.Probe ->TotalRowSize ];
323+ TTable::Tuple probeRow{thisRow, thisPackResult.Overflow .data ()};
324+ Table_.Lookup (probeRow, [&](TTable::Tuple matchedBuildRow) {
325+ consumeOneOrTwoTuples (TSides<TTable::Tuple>{.Build = matchedBuildRow, .Probe = probeRow});
326+ });
327+ }
328+ }
329+
330+ return resEnum;
331+ }
332+
333+ return EFetchResult::Finish;
334+ }
335+
336+ private:
337+ const NUdf::TLoggerPtr Logger_;
338+ const NUdf::TLogComponentId LogComponent_;
339+ TSides<Source> Sources_;
340+ TSides<const NPackedTuple::TTupleLayout*> Layouts_;
341+ TTable Table_;
342+ IBlockLayoutConverter::TPackResult BuildData_;
343+ std::vector<IBlockLayoutConverter::TPackResult> BuildChunks_;
344+ };
345+
211346} // namespace NKikimr::NMiniKQL
0 commit comments