Skip to content

Commit 66bc87d

Browse files
authored
datastore: refactor ascending and lazy begin views (#2769)
refactor ascending template with if_view refactor lazy begin pattern with LazyView
2 parents 724ff3b + ed5d473 commit 66bc87d

11 files changed

+104
-60
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
Copyright 2025 The Silkworm Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
#pragma once
18+
19+
#include <functional>
20+
#include <optional>
21+
#include <ranges>
22+
23+
namespace silkworm::ranges {
24+
25+
template <std::invocable TRangeFactory, std::ranges::range TRange = decltype(std::invoke(std::declval<TRangeFactory>()))>
26+
class LazyView : public std::ranges::view_interface<LazyView<TRangeFactory, TRange>> {
27+
public:
28+
LazyView() = default;
29+
30+
explicit LazyView(TRangeFactory&& range_factory) : range_factory_{std::move(range_factory)} {}
31+
32+
LazyView(LazyView&&) = default;
33+
34+
LazyView& operator=(LazyView&& other) noexcept {
35+
range_factory_ = std::exchange(std::move(other.range_factory_), std::nullopt);
36+
range_ = std::exchange(std::move(other.range_), std::nullopt);
37+
return this;
38+
};
39+
40+
std::ranges::iterator_t<TRange> begin() { return std::ranges::begin(range()); }
41+
std::ranges::sentinel_t<TRange> end() { return std::ranges::end(range()); }
42+
43+
private:
44+
TRange& range() {
45+
if (!range_) {
46+
range_.emplace(std::invoke(*range_factory_));
47+
}
48+
return *range_;
49+
}
50+
51+
std::optional<TRangeFactory> range_factory_;
52+
std::optional<TRange> range_;
53+
};
54+
55+
template <class TRangeFactory>
56+
LazyView<TRangeFactory> lazy(TRangeFactory&& range_factory) {
57+
return LazyView<TRangeFactory>{std::forward<TRangeFactory>(range_factory)};
58+
}
59+
60+
} // namespace silkworm::ranges

silkworm/db/datastore/inverted_index_range_by_key_query.hpp

+9-10
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#pragma once
1818

1919
#include "common/ranges/concat_view.hpp"
20+
#include "common/ranges/if_view.hpp"
2021
#include "kvdb/database.hpp"
2122
#include "kvdb/inverted_index_range_by_key_query.hpp"
2223
#include "snapshots/inverted_index_range_by_key_query.hpp"
@@ -50,17 +51,15 @@ struct InvertedIndexRangeByKeyQuery {
5051
static_assert(std::same_as<Key1, Key2>);
5152
using Key = Key1;
5253

53-
template <bool ascending = true>
54-
auto exec(Key key, TimestampRange ts_range) {
55-
if constexpr (ascending) {
56-
return silkworm::views::concat(
57-
query2_.template exec<ascending>(key, ts_range),
58-
query1_.exec(key, ts_range, ascending));
59-
} else {
60-
return silkworm::views::concat(
54+
auto exec(Key key, TimestampRange ts_range, bool ascending) {
55+
return silkworm::views::if_view(
56+
ascending,
57+
silkworm::views::concat(
58+
query2_.exec(key, ts_range, ascending),
59+
query1_.exec(key, ts_range, ascending)),
60+
silkworm::views::concat(
6161
query1_.exec(key, ts_range, ascending),
62-
query2_.template exec<ascending>(key, ts_range));
63-
}
62+
query2_.exec(key, ts_range, ascending)));
6463
}
6564

6665
private:

silkworm/db/datastore/kvdb/domain_range_latest_query.hpp

+3-4
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@
1818

1919
#include <ranges>
2020
#include <utility>
21-
#include <variant>
2221

2322
#include <silkworm/core/common/assert.hpp>
2423

2524
#include "../common/pair_get.hpp"
2625
#include "../common/ranges/caching_view.hpp"
26+
#include "../common/ranges/lazy_view.hpp"
2727
#include "../common/ranges/unique_view.hpp"
2828
#include "cursor_iterator.hpp"
2929
#include "domain.hpp"
@@ -108,11 +108,10 @@ struct DomainRangeLatestQuery {
108108
Slice key_end_slice = key_end_encoder.encode();
109109
Bytes key_end_data = Bytes{from_slice(key_end_slice)};
110110

111-
auto exec_func = [query = *this, key_start = std::move(key_start_data), key_end = std::move(key_end_data), ascending](std::monostate) mutable {
111+
auto exec_func = [query = *this, key_start = std::move(key_start_data), key_end = std::move(key_end_data), ascending]() mutable {
112112
return query.exec_with_eager_begin(std::move(key_start), std::move(key_end), ascending);
113113
};
114-
// turn into a lazy view that runs exec_func only when iteration is started using range::begin()
115-
return std::views::single(std::monostate{}) | std::views::transform(std::move(exec_func)) | std::views::join;
114+
return silkworm::ranges::lazy(std::move(exec_func));
116115
}
117116
};
118117

silkworm/db/datastore/kvdb/history_range_by_keys_query.hpp

+3-4
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@
1818

1919
#include <ranges>
2020
#include <utility>
21-
#include <variant>
2221

2322
#include <silkworm/core/common/assert.hpp>
2423

2524
#include "../common/pair_get.hpp"
2625
#include "../common/ranges/caching_view.hpp"
26+
#include "../common/ranges/lazy_view.hpp"
2727
#include "../common/ranges/unique_view.hpp"
2828
#include "cursor_iterator.hpp"
2929
#include "history.hpp"
@@ -166,11 +166,10 @@ struct HistoryRangeByKeysQuery {
166166
Slice key_end_slice = key_end_encoder.encode();
167167
Bytes key_end_data = Bytes{from_slice(key_end_slice)};
168168

169-
auto exec_func = [query = *this, key_start = std::move(key_start_data), key_end = std::move(key_end_data), timestamp, ascending](std::monostate) mutable {
169+
auto exec_func = [query = *this, key_start = std::move(key_start_data), key_end = std::move(key_end_data), timestamp, ascending]() mutable {
170170
return query.exec_with_eager_begin(std::move(key_start), std::move(key_end), timestamp, ascending);
171171
};
172-
// turn into a lazy view that runs exec_func only when iteration is started using range::begin()
173-
return std::views::single(std::monostate{}) | std::views::transform(std::move(exec_func)) | std::views::join;
172+
return silkworm::ranges::lazy(std::move(exec_func));
174173
}
175174
};
176175

silkworm/db/datastore/kvdb/history_range_in_period_query.hpp

+3-4
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@
1818

1919
#include <functional>
2020
#include <ranges>
21-
#include <variant>
2221

2322
#include <silkworm/core/common/assert.hpp>
2423

2524
#include "../common/ranges/caching_view.hpp"
25+
#include "../common/ranges/lazy_view.hpp"
2626
#include "../common/timestamp.hpp"
2727
#include "cursor_iterator.hpp"
2828
#include "history.hpp"
@@ -153,11 +153,10 @@ struct HistoryRangeInPeriodQuery {
153153
}
154154

155155
auto exec(TimestampRange ts_range, bool ascending) {
156-
auto exec_func = [query = *this, ts_range, ascending](std::monostate) mutable {
156+
auto exec_func = [query = *this, ts_range, ascending]() mutable {
157157
return query.exec_with_eager_begin(ts_range, ascending);
158158
};
159-
// turn into a lazy view that runs exec_func only when iteration is started using range::begin()
160-
return std::views::single(std::monostate{}) | std::views::transform(std::move(exec_func)) | std::views::join;
159+
return silkworm::ranges::lazy(std::move(exec_func));
161160
}
162161
};
163162

silkworm/db/datastore/kvdb/inverted_index_range_by_key_query.hpp

+3-4
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818

1919
#include <ranges>
2020
#include <utility>
21-
#include <variant>
2221

22+
#include "../common/ranges/lazy_view.hpp"
2323
#include "../common/timestamp.hpp"
2424
#include "codec.hpp"
2525
#include "cursor_iterator.hpp"
@@ -83,11 +83,10 @@ struct InvertedIndexRangeByKeyQuery {
8383
}
8484

8585
auto exec(Key key, TimestampRange ts_range, bool ascending) {
86-
auto exec_func = [query = *this, key = std::move(key), ts_range, ascending](std::monostate) mutable {
86+
auto exec_func = [query = *this, key = std::move(key), ts_range, ascending]() mutable {
8787
return query.exec_with_eager_begin(std::move(key), ts_range, ascending);
8888
};
89-
// turn into a lazy view that runs exec_func only when iteration is started using range::begin()
90-
return std::views::single(std::monostate{}) | std::views::transform(std::move(exec_func)) | std::views::join;
89+
return silkworm::ranges::lazy(std::move(exec_func));
9190
}
9291
};
9392

silkworm/db/datastore/snapshots/domain_range_latest_query.hpp

+3-4
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@
1919
#include <iterator>
2020
#include <ranges>
2121
#include <utility>
22-
#include <variant>
2322
#include <vector>
2423

2524
#include <silkworm/core/common/assert.hpp>
2625

2726
#include "../common/entity_name.hpp"
2827
#include "../common/pair_get.hpp"
2928
#include "../common/ranges/caching_view.hpp"
29+
#include "../common/ranges/lazy_view.hpp"
3030
#include "../common/ranges/merge_many_view.hpp"
3131
#include "../common/ranges/owning_view.hpp"
3232
#include "common/codec.hpp"
@@ -56,11 +56,10 @@ struct DomainRangeLatestSegmentQuery {
5656
}
5757

5858
auto exec(Bytes key_start, Bytes key_end, bool ascending) {
59-
auto exec_func = [query = *this, key_start = std::move(key_start), key_end = std::move(key_end), ascending](std::monostate) mutable {
59+
auto exec_func = [query = *this, key_start = std::move(key_start), key_end = std::move(key_end), ascending]() mutable {
6060
return query.exec_with_eager_begin(std::move(key_start), std::move(key_end), ascending);
6161
};
62-
// turn into a lazy view that runs exec_func only when iteration is started using range::begin()
63-
return std::views::single(std::monostate{}) | std::views::transform(std::move(exec_func)) | std::views::join;
62+
return silkworm::ranges::lazy(std::move(exec_func));
6463
}
6564

6665
private:

silkworm/db/datastore/snapshots/history_range_by_keys_query.hpp

+3-4
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@
1919
#include <iterator>
2020
#include <ranges>
2121
#include <utility>
22-
#include <variant>
2322
#include <vector>
2423

2524
#include <silkworm/core/common/assert.hpp>
2625

2726
#include "../common/entity_name.hpp"
2827
#include "../common/pair_get.hpp"
2928
#include "../common/ranges/caching_view.hpp"
29+
#include "../common/ranges/lazy_view.hpp"
3030
#include "../common/ranges/merge_many_view.hpp"
3131
#include "../common/ranges/owning_view.hpp"
3232
#include "common/codec.hpp"
@@ -93,11 +93,10 @@ struct HistoryRangeByKeysSegmentQuery {
9393
}
9494

9595
auto exec(Bytes key_start, Bytes key_end, datastore::Timestamp timestamp, bool ascending) {
96-
auto exec_func = [query = *this, key_start = std::move(key_start), key_end = std::move(key_end), timestamp, ascending](std::monostate) mutable {
96+
auto exec_func = [query = *this, key_start = std::move(key_start), key_end = std::move(key_end), timestamp, ascending]() mutable {
9797
return query.exec_with_eager_begin(std::move(key_start), std::move(key_end), timestamp, ascending);
9898
};
99-
// turn into a lazy view that runs exec_func only when iteration is started using range::begin()
100-
return std::views::single(std::monostate{}) | std::views::transform(std::move(exec_func)) | std::views::join;
99+
return silkworm::ranges::lazy(std::move(exec_func));
101100
}
102101

103102
private:

silkworm/db/datastore/snapshots/inverted_index_find_by_key_segment_query.hpp

+8-10
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <optional>
2020
#include <ranges>
2121

22+
#include "../common/ranges/if_view.hpp"
2223
#include "../common/ranges/owning_view.hpp"
2324
#include "../common/timestamp.hpp"
2425
#include "common/raw_codec.hpp"
@@ -28,13 +29,11 @@
2829

2930
namespace silkworm::snapshots {
3031

31-
template <bool ascending = true>
32-
auto timestamp_range_filter(elias_fano::EliasFanoList32 list, datastore::TimestampRange ts_range) {
33-
if constexpr (ascending) {
34-
return silkworm::ranges::owning_view(std::move(list)) | std::views::all | std::views::filter(ts_range.contains_predicate());
35-
} else {
36-
return silkworm::ranges::owning_view(std::move(list)) | std::views::reverse | std::views::filter(ts_range.contains_predicate());
37-
}
32+
inline auto timestamp_range_filter(elias_fano::EliasFanoList32 list, datastore::TimestampRange ts_range, bool ascending) {
33+
return silkworm::views::if_view(
34+
ascending,
35+
silkworm::ranges::owning_view(std::move(list)) | std::views::all | std::views::filter(ts_range.contains_predicate()),
36+
silkworm::ranges::owning_view(std::move(list)) | std::views::reverse | std::views::filter(ts_range.contains_predicate()));
3837
}
3938

4039
template <EncoderConcept TKeyEncoder>
@@ -70,9 +69,8 @@ struct InvertedIndexFindByKeySegmentQuery {
7069
return std::nullopt;
7170
}
7271

73-
template <bool ascending = true>
74-
auto exec_filter(Key key, datastore::TimestampRange ts_range) {
75-
return timestamp_range_filter<ascending>(exec(std::move(key)).value_or(elias_fano::EliasFanoList32::empty_list()), ts_range);
72+
auto exec_filter(Key key, datastore::TimestampRange ts_range, bool ascending) {
73+
return timestamp_range_filter(exec(std::move(key)).value_or(elias_fano::EliasFanoList32::empty_list()), ts_range, ascending);
7674
}
7775

7876
private:

silkworm/db/datastore/snapshots/inverted_index_range_by_key_query.hpp

+3-4
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,10 @@ struct InvertedIndexRangeByKeyQuery {
3636

3737
using Key = decltype(TKeyEncoder::value);
3838

39-
template <bool ascending = true>
40-
auto exec(Key key, datastore::TimestampRange ts_range) {
41-
auto timestamps_in_bundle = [entity_name = entity_name_, key = std::move(key), ts_range](const std::shared_ptr<SnapshotBundle>& bundle) {
39+
auto exec(Key key, datastore::TimestampRange ts_range, bool ascending) {
40+
auto timestamps_in_bundle = [entity_name = entity_name_, key = std::move(key), ts_range, ascending](const std::shared_ptr<SnapshotBundle>& bundle) {
4241
InvertedIndexFindByKeySegmentQuery<TKeyEncoder> query{*bundle, entity_name};
43-
return query.template exec_filter<ascending>(key, ts_range);
42+
return query.exec_filter(key, ts_range, ascending);
4443
};
4544

4645
return silkworm::ranges::owning_view(repository_.bundles_intersecting_range(ts_range, ascending)) |

silkworm/db/kv/api/local_transaction.cpp

+6-12
Original file line numberDiff line numberDiff line change
@@ -244,19 +244,13 @@ Task<PaginatedTimestamps> LocalTransaction::index_range(IndexRangeRequest reques
244244
datastore::TimestampRange ts_range = as_datastore_ts_range({request.from_timestamp, request.to_timestamp}, !request.ascending_order);
245245
const size_t limit = (request.limit == kUnlimited) ? std::numeric_limits<size_t>::max() : static_cast<size_t>(request.limit);
246246

247-
api::PaginatedTimestamps::PageResult result;
248247
// TODO: support pagination: apply page_size using std::views::chunk, save the range for future requests using page_token and return the first chunk
249-
if (request.ascending_order) {
250-
auto timestamps = query.exec<true>(request.key, std::move(ts_range)) |
251-
std::views::transform([](datastore::Timestamp ts) { return static_cast<Timestamp>(ts); }) |
252-
std::views::take(limit);
253-
result.values = vector_from_range(std::move(timestamps));
254-
} else {
255-
auto timestamps = query.exec<false>(request.key, std::move(ts_range)) |
256-
std::views::transform([](datastore::Timestamp ts) { return static_cast<Timestamp>(ts); }) |
257-
std::views::take(limit);
258-
result.values = vector_from_range(std::move(timestamps));
259-
}
248+
auto timestamps = query.exec(request.key, std::move(ts_range), request.ascending_order) |
249+
std::views::transform([](datastore::Timestamp ts) { return static_cast<Timestamp>(ts); }) |
250+
std::views::take(limit);
251+
252+
api::PaginatedTimestamps::PageResult result;
253+
result.values = vector_from_range(std::move(timestamps));
260254

261255
co_return result;
262256
};

0 commit comments

Comments
 (0)