Skip to content

Commit 36842b6

Browse files
otabek1998joka921
authored andcommitted
Migrate coroutines in src/engine/IndexScan to C++17 (#2316)
Co-authored-by: Johannes Kalmbach <johannes.kalmbach@gmail.com>
1 parent d3e5e45 commit 36842b6

File tree

8 files changed

+310
-115
lines changed

8 files changed

+310
-115
lines changed

src/engine/IndexScan.cpp

Lines changed: 100 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
#include "engine/QueryExecutionTree.h"
1414
#include "index/IndexImpl.h"
1515
#include "parser/ParsedQuery.h"
16+
#include "util/Generator.h"
17+
#include "util/GeneratorConverter.h"
18+
#include "util/InputRangeUtils.h"
19+
#include "util/Iterators.h"
1620

1721
using std::string;
1822
using LazyScanMetadata = CompressedRelationReader::LazyScanMetadata;
@@ -235,10 +239,11 @@ IndexScan::makeCopyWithPrefilteredScanSpecAndBlocks(
235239
}
236240

237241
// _____________________________________________________________________________
238-
Result::Generator IndexScan::chunkedIndexScan() const {
239-
for (IdTable& idTable : getLazyScan()) {
240-
co_yield {std::move(idTable), LocalVocab{}};
241-
}
242+
Result::LazyResult IndexScan::chunkedIndexScan() const {
243+
return Result::LazyResult{
244+
ad_utility::CachingTransformInputRange(getLazyScan(), [](auto& table) {
245+
return Result::IdTableVocabPair{std::move(table), LocalVocab{}};
246+
})};
242247
}
243248

244249
// _____________________________________________________________________________
@@ -372,13 +377,13 @@ Permutation::IdTableGenerator IndexScan::getLazyScan(
372377
auto lazyScanAllCols = getScanPermutation().lazyScan(
373378
scanSpecAndBlocks_, filteredBlocks, additionalColumns(),
374379
cancellationHandle_, locatedTriplesSnapshot(), getLimitOffset());
375-
auto& detailsRef = co_await cppcoro::getDetails;
376-
lazyScanAllCols.setDetailsPointer(&detailsRef);
377-
auto applySubset = makeApplyColumnSubset();
378380

379-
for (auto& table : lazyScanAllCols) {
380-
co_yield applySubset(std::move(table));
381-
}
381+
return cppcoro::fromInputRange(
382+
ad_utility::InputRangeTypeErased<IdTable, LazyScanMetadata>(
383+
ad_utility::CachingTransformInputRange<
384+
ad_utility::OwningView<Permutation::IdTableGenerator>,
385+
decltype(makeApplyColumnSubset()), LazyScanMetadata>{
386+
std::move(lazyScanAllCols), makeApplyColumnSubset()}));
382387
};
383388

384389
// _____________________________________________________________________________
@@ -514,10 +519,7 @@ struct IndexScan::SharedGeneratorState {
514519
iterator_ = generator_.begin();
515520
}
516521
auto& iterator = iterator_.value();
517-
while (iterator != generator_.end()) {
518-
if (!iterator->idTable_.empty()) {
519-
break;
520-
}
522+
while (iterator != generator_.end() && iterator->idTable_.empty()) {
521523
++iterator;
522524
}
523525
doneFetching_ = iterator_ == generator_.end();
@@ -586,75 +588,104 @@ struct IndexScan::SharedGeneratorState {
586588
};
587589

588590
// _____________________________________________________________________________
589-
Result::Generator IndexScan::createPrefilteredJoinSide(
591+
Result::LazyResult IndexScan::createPrefilteredJoinSide(
590592
std::shared_ptr<SharedGeneratorState> innerState) {
591-
if (innerState->hasUndef()) {
592-
AD_CORRECTNESS_CHECK(innerState->prefetchedValues_.empty());
593-
for (auto& value : ql::ranges::subrange{innerState->iterator_.value(),
594-
innerState->generator_.end()}) {
595-
co_yield value;
596-
}
597-
co_return;
598-
}
599-
auto& prefetchedValues = innerState->prefetchedValues_;
600-
while (true) {
601-
if (prefetchedValues.empty()) {
602-
if (innerState->doneFetching_) {
603-
co_return;
604-
}
605-
innerState->fetch();
606-
AD_CORRECTNESS_CHECK(!prefetchedValues.empty() ||
607-
innerState->doneFetching_);
608-
}
609-
// Make a defensive copy of the values to avoid modification during
610-
// iteration when yielding.
611-
auto copy = std::move(prefetchedValues);
612-
// Moving out does not necessarily clear the values, so we do it explicitly.
613-
prefetchedValues.clear();
614-
for (auto& value : copy) {
615-
co_yield value;
616-
}
617-
}
593+
using LoopControl = ad_utility::LoopControl<Result::IdTableVocabPair>;
594+
595+
auto range = ad_utility::InputRangeFromLoopControlGet{
596+
[state = std::move(innerState)]() mutable {
597+
// Handle UNDEF case: pass through remaining input
598+
if (state->hasUndef()) {
599+
if (!state->iterator_.has_value()) {
600+
state->iterator_ = state->generator_.begin();
601+
}
602+
return LoopControl::breakWithYieldAll(ql::ranges::subrange(
603+
state->iterator_.value(), state->generator_.end()));
604+
}
605+
606+
auto& prefetched = state->prefetchedValues_;
607+
608+
if (prefetched.empty() && !state->doneFetching_) {
609+
state->fetch();
610+
}
611+
612+
if (prefetched.empty()) {
613+
AD_CORRECTNESS_CHECK(state->doneFetching_);
614+
return LoopControl::makeBreak();
615+
}
616+
617+
// Make a defensive copy of the values to avoid modification during
618+
// iteration when yielding.
619+
auto copy = std::move(prefetched);
620+
prefetched.clear();
621+
622+
// Yield all the newly found values
623+
return LoopControl::yieldAll(std::move(copy));
624+
}};
625+
return Result::LazyResult{std::move(range)};
618626
}
619627

620628
// _____________________________________________________________________________
621-
Result::Generator IndexScan::createPrefilteredIndexScanSide(
629+
Result::LazyResult IndexScan::createPrefilteredIndexScanSide(
622630
std::shared_ptr<SharedGeneratorState> innerState) {
623-
if (innerState->hasUndef()) {
624-
for (auto& pair : chunkedIndexScan()) {
625-
co_yield pair;
626-
}
627-
co_return;
628-
}
629-
LazyScanMetadata metadata;
630-
auto& pendingBlocks = innerState->pendingBlocks_;
631-
while (true) {
632-
if (pendingBlocks.empty()) {
633-
if (innerState->doneFetching_) {
634-
metadata.numBlocksAll_ = innerState->metaBlocks_.sizeBlockMetadata_;
635-
updateRuntimeInfoForLazyScan(metadata);
636-
co_return;
637-
}
638-
innerState->fetch();
639-
}
640-
auto scan = getLazyScan(std::move(pendingBlocks));
641-
AD_CORRECTNESS_CHECK(pendingBlocks.empty());
642-
for (IdTable& idTable : scan) {
643-
co_yield {std::move(idTable), LocalVocab{}};
644-
}
645-
metadata.aggregate(scan.details());
646-
}
631+
using LoopControl = ad_utility::LoopControl<Result::IdTableVocabPair>;
632+
633+
auto range = ad_utility::InputRangeFromLoopControlGet{
634+
[this, state = std::move(innerState),
635+
metadata = LazyScanMetadata{}]() mutable {
636+
// Handle UNDEF case using LoopControl pattern
637+
if (state->hasUndef()) {
638+
return LoopControl::breakWithYieldAll(chunkedIndexScan());
639+
}
640+
641+
auto& pendingBlocks = state->pendingBlocks_;
642+
643+
while (pendingBlocks.empty()) {
644+
if (state->doneFetching_) {
645+
metadata.numBlocksAll_ = state->metaBlocks_.sizeBlockMetadata_;
646+
updateRuntimeInfoForLazyScan(metadata);
647+
return LoopControl::makeBreak();
648+
}
649+
state->fetch();
650+
}
651+
652+
// We now have non-empty pending blocks
653+
auto scan = getLazyScan(std::move(pendingBlocks));
654+
AD_CORRECTNESS_CHECK(pendingBlocks.empty());
655+
656+
// Capture scan details by reference so we get the updated values
657+
const auto& scanDetails = scan.details();
658+
659+
// Transform the scan to Result::IdTableVocabPair and yield all
660+
auto transformedScan = ad_utility::CachingTransformInputRange(
661+
std::move(scan), [](auto& table) {
662+
return Result::IdTableVocabPair{std::move(table), LocalVocab{}};
663+
});
664+
665+
// Use CallbackOnEndView to aggregate metadata after scan is consumed
666+
auto callback = ad_utility::makeAssignableLambda(
667+
[&metadata, &scanDetails]() mutable {
668+
metadata.aggregate(scanDetails);
669+
});
670+
671+
auto scanWithCallback = ad_utility::CallbackOnEndView{
672+
std::move(transformedScan), std::move(callback)};
673+
return LoopControl::yieldAll(std::move(scanWithCallback));
674+
}};
675+
return Result::LazyResult{std::move(range)};
647676
}
648677

649678
// _____________________________________________________________________________
650-
std::pair<Result::Generator, Result::Generator> IndexScan::prefilterTables(
679+
std::pair<Result::LazyResult, Result::LazyResult> IndexScan::prefilterTables(
651680
Result::LazyResult input, ColumnIndex joinColumn) {
652681
AD_CORRECTNESS_CHECK(numVariables_ <= 3 && numVariables_ > 0);
653682
auto metaBlocks = getMetadataForScan();
654683

655684
if (!metaBlocks.has_value()) {
656-
return {Result::Generator{}, Result::Generator{}};
685+
// Return empty results
686+
return {Result::LazyResult{}, Result::LazyResult{}};
657687
}
688+
658689
auto state = std::make_shared<SharedGeneratorState>(
659690
std::move(input), joinColumn, std::move(metaBlocks.value()));
660691
return {createPrefilteredJoinSide(state),

src/engine/IndexScan.h

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -113,24 +113,24 @@ class IndexScan final : public Operation {
113113
// `input` to speed up join algorithms when no undef values are presend. When
114114
// there are undef values, the second generator represents the full index
115115
// scan.
116-
std::pair<Result::Generator, Result::Generator> prefilterTables(
116+
std::pair<Result::LazyResult, Result::LazyResult> prefilterTables(
117117
Result::LazyResult input, ColumnIndex joinColumn);
118118

119119
private:
120-
// Implementation detail that allows to consume a generator from two other
121-
// cooperating generators. Needs to be forward declared as it is used by
120+
// Implementation detail that allows to consume a lazy range from two other
121+
// cooperating ranges. Needs to be forward declared as it is used by
122122
// several member functions below.
123123
struct SharedGeneratorState;
124124

125-
// Helper function that creates a generator that re-yields the generator
125+
// Helper function that creates a lazy range that re-yields the input
126126
// wrapped by `innerState`.
127-
static Result::Generator createPrefilteredJoinSide(
127+
static Result::LazyResult createPrefilteredJoinSide(
128128
std::shared_ptr<SharedGeneratorState> innerState);
129129

130-
// Helper function that creates a generator yielding prefiltered rows of this
131-
// index scan according to the block metadata, that match the tables yielded
132-
// by the generator wrapped by `innerState`.
133-
Result::Generator createPrefilteredIndexScanSide(
130+
// Helper function that creates a lazy range yielding prefiltered rows of
131+
// this index scan according to the block metadata, that match the tables
132+
// yielded by the input wrapped by `innerState`.
133+
Result::LazyResult createPrefilteredIndexScanSide(
134134
std::shared_ptr<SharedGeneratorState> innerState);
135135

136136
// TODO<joka921> Make the `getSizeEstimateBeforeLimit()` function `const` for
@@ -220,7 +220,7 @@ class IndexScan final : public Operation {
220220
ScanSpecAndBlocks scanSpecAndBlocks) const;
221221

222222
// Return the (lazy) `IdTable` for this `IndexScan` in chunks.
223-
Result::Generator chunkedIndexScan() const;
223+
Result::LazyResult chunkedIndexScan() const;
224224
// Get the `IdTable` for this `IndexScan` in one piece.
225225
IdTable materializedIndexScan() const;
226226

src/util/Generator.h

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ namespace cppcoro {
2222
struct GetDetails {};
2323
static constexpr GetDetails getDetails;
2424

25+
template <typename Details>
26+
struct SetDetailsPointer {
27+
Details* pointer_;
28+
};
29+
template <typename Details>
30+
struct SetDetails {
31+
Details details_;
32+
};
33+
2534
// This struct is used as the default of the details object for the case that
2635
// there are no details
2736
struct NoDetails {};
@@ -93,7 +102,40 @@ class generator_promise {
93102
return {*this};
94103
}
95104

105+
struct SetDetailsPointerAwaiter {
106+
SetDetailsPointerAwaiter(generator_promise& promise,
107+
struct SetDetailsPointer<Details> details) {
108+
promise.setDetailsPointer(details.pointer_);
109+
}
110+
constexpr bool await_ready() const { return true; }
111+
constexpr bool await_suspend(std::coroutine_handle<>) const noexcept {
112+
return false;
113+
}
114+
constexpr void await_resume() const noexcept {}
115+
};
116+
117+
struct SetDetailsAwaiter {
118+
SetDetailsAwaiter(generator_promise& promise,
119+
struct SetDetails<Details> details) {
120+
promise.setDetails(std::move(details.details_));
121+
}
122+
constexpr bool await_ready() const { return true; }
123+
constexpr bool await_suspend(std::coroutine_handle<>) const noexcept {
124+
return false;
125+
}
126+
constexpr void await_resume() const noexcept {}
127+
};
128+
96129
static constexpr bool hasDetails = !std::is_same_v<Details, NoDetails>;
130+
SetDetailsPointerAwaiter await_transform(SetDetailsPointer<Details> details)
131+
requires hasDetails {
132+
return {*this, details};
133+
}
134+
SetDetailsAwaiter await_transform(SetDetails<Details> details)
135+
requires hasDetails {
136+
return {*this, details};
137+
}
138+
97139
Details& details() requires hasDetails {
98140
return std::holds_alternative<Details>(m_details)
99141
? std::get<Details>(m_details)
@@ -105,6 +147,10 @@ class generator_promise {
105147
m_details = pointer;
106148
}
107149

150+
void setDetails(Details details) requires hasDetails {
151+
m_details = std::move(details);
152+
}
153+
108154
private:
109155
pointer_type m_value;
110156
std::exception_ptr m_exception;
@@ -295,7 +341,6 @@ T getSingleElement(generator<T, Details> g) {
295341
AD_CORRECTNESS_CHECK(++it == g.end());
296342
return t;
297343
}
298-
299344
} // namespace cppcoro
300345

301346
#endif

src/util/GeneratorConverter.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,18 @@ namespace cppcoro {
1818
template <typename T, typename D>
1919
generator<T, D> fromInputRange(ad_utility::InputRangeTypeErased<T, D> range) {
2020
if constexpr (!std::is_same_v<D, ad_utility::NoDetails>) {
21-
auto& detailsRef = co_await cppcoro::getDetails;
22-
range.setDetailsPointer(&detailsRef);
21+
co_await cppcoro::SetDetailsPointer<D>{&range.details()};
2322
}
2423

2524
for (auto& value : range) {
2625
co_yield value;
2726
}
27+
28+
// The range is about to be destroyed, copy the details.
29+
if constexpr (!std::is_same_v<D, ad_utility::NoDetails>) {
30+
co_await cppcoro::SetDetails<std::decay_t<decltype(range.details())>>{
31+
std::move(range.details())};
32+
}
2833
}
2934
} // namespace cppcoro
3035

0 commit comments

Comments
 (0)