Skip to content

Commit

Permalink
datastore: HistoryGetQuery
Browse files Browse the repository at this point in the history
  • Loading branch information
battlmonstr committed Jan 28, 2025
1 parent 8859967 commit 6606785
Show file tree
Hide file tree
Showing 7 changed files with 262 additions and 11 deletions.
2 changes: 1 addition & 1 deletion cmake/copyright.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ set(COPYRIGHT_HEADER_TEMPLATE
"
)

set(SILKWORM_COPYRIGHT_YEARS "2022" "2023" "2024")
set(SILKWORM_COPYRIGHT_YEARS "2022" "2023" "2024" "2025")

function(check file_path)
string(LENGTH "${COPYRIGHT_HEADER_TEMPLATE}" header_len)
Expand Down
18 changes: 16 additions & 2 deletions silkworm/db/datastore/kvdb/domain_put_latest_query_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@

#include "domain_put_latest_query.hpp"

#include <functional>

#include <catch2/catch_template_test_macros.hpp>
#include <catch2/catch_test_macros.hpp>

#include <silkworm/infra/common/directories.hpp>
Expand All @@ -39,13 +42,24 @@ bool operator==(const Result& lhs, const Result& rhs) {
return (lhs.value == rhs.value) && (lhs.step == rhs.step);
};

TEST_CASE("DomainPutLatestQuery") {
// by default has_large_values = false, is_multi_value = true
using DomainDefault = std::identity;

struct DomainWithLargeValues {
Schema::DomainDef& operator()(Schema::DomainDef& domain) const {
domain.enable_large_values().values_disable_multi_value();
return domain;
}
};

TEMPLATE_TEST_CASE("DomainPutLatestQuery", "", DomainDefault, DomainWithLargeValues) {
const TemporaryDirectory tmp_dir;
::mdbx::env_managed env = open_env(EnvConfig{.path = tmp_dir.path().string(), .create = true, .in_memory = true});

EntityName name{"Test"};
Schema::DatabaseDef schema;
schema.domain(name);
TestType domain_config;
[[maybe_unused]] auto _ = domain_config(schema.domain(name));

Database db{std::move(env), schema};
db.create_tables();
Expand Down
37 changes: 37 additions & 0 deletions silkworm/db/datastore/kvdb/history_codecs.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
Copyright 2025 The Silkworm Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#pragma once

#include "../common/timestamp.hpp"
#include "kvts_codec.hpp"
#include "timestamp_codec.hpp"

namespace silkworm::datastore::kvdb {

template <EncoderConcept TEncoder>
using HistoryKeyEncoder = KVTSKeyEncoder<TEncoder, TimestampEncoder>;

template <EncoderConcept TEncoder>
using HistoryValueEncoder = KVTSValueEncoder<TEncoder, TimestampEncoder>;

template <DecoderConcept TDecoder>
using HistoryKeyDecoder = KVTSKeyDecoder<TDecoder, TimestampDecoder, sizeof(Timestamp)>;

template <DecoderConcept TDecoder>
using HistoryValueDecoder = KVTSValueDecoder<TDecoder, TimestampDecoder, sizeof(Timestamp)>;

} // namespace silkworm::datastore::kvdb
81 changes: 81 additions & 0 deletions silkworm/db/datastore/kvdb/history_get_query.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
Copyright 2025 The Silkworm Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#pragma once

#include <tl/expected.hpp>

#include "../common/timestamp.hpp"
#include "history.hpp"
#include "history_codecs.hpp"
#include "mdbx.hpp"

namespace silkworm::datastore::kvdb {

template <EncoderConcept TKeyEncoder, DecoderConcept TValueDecoder>
struct HistoryGetQuery {
ROTxn& tx;
History entity;

using Key = decltype(TKeyEncoder::value);
using Value = decltype(TValueDecoder::value);

enum class [[nodiscard]] NoValueReason {
kNotFound,
kDeleted,
};

tl::expected<Value, NoValueReason> exec(const Key& key, Timestamp timestamp) {
HistoryKeyEncoder<TKeyEncoder> key_encoder{entity.has_large_values};
key_encoder.value.key.value = key;
key_encoder.value.timestamp.value = timestamp;
Slice key_slice = key_encoder.encode();

CursorResult result{Slice{}, Slice{}, /* done = */ false};
if (entity.has_large_values) {
result = tx.ro_cursor(entity.values_table)->lower_bound(key_slice, false);
if (result) {
HistoryKeyDecoder<RawDecoder<ByteView>> key_decoder{entity.has_large_values};
key_decoder.decode(key_slice);
ByteView key_data = key_decoder.value.key.value;
key_decoder.decode(result.key);
ByteView key_data_found = key_decoder.value.key.value;
if (key_data_found != key_data) {
result = CursorResult{Slice{}, Slice{}, /* done = */ false};
}
}
} else {
HistoryValueEncoder<RawEncoder<ByteView>> value_encoder{entity.has_large_values};
value_encoder.value.timestamp.value = timestamp;
value_encoder.value.value.value = ByteView{};
Slice value_slice = value_encoder.encode();

result = tx.ro_cursor_dup_sort(entity.values_table)->lower_bound_multivalue(key_slice, value_slice, false);
}

if (!result) return tl::unexpected{NoValueReason::kNotFound};

HistoryValueDecoder<RawDecoder<ByteView>> empty_value_decoder{entity.has_large_values};
empty_value_decoder.decode(result.value);
if (empty_value_decoder.value.value.value.empty()) return tl::unexpected{NoValueReason::kDeleted};

HistoryValueDecoder<TValueDecoder> value_decoder{entity.has_large_values};
value_decoder.decode(result.value);
return std::move(value_decoder.value.value.value);
}
};

} // namespace silkworm::datastore::kvdb
9 changes: 1 addition & 8 deletions silkworm/db/datastore/kvdb/history_put_query.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,12 @@

#include "../common/timestamp.hpp"
#include "history.hpp"
#include "history_codecs.hpp"
#include "inverted_index_put_query.hpp"
#include "kvts_codec.hpp"
#include "mdbx.hpp"
#include "timestamp_codec.hpp"

namespace silkworm::datastore::kvdb {

template <EncoderConcept TEncoder>
using HistoryKeyEncoder = KVTSKeyEncoder<TEncoder, TimestampEncoder>;

template <EncoderConcept TEncoder>
using HistoryValueEncoder = KVTSValueEncoder<TEncoder, TimestampEncoder>;

template <EncoderConcept TKeyEncoder, EncoderConcept TValueEncoder>
struct HistoryPutQuery {
RWTxn& tx;
Expand Down
125 changes: 125 additions & 0 deletions silkworm/db/datastore/kvdb/history_put_query_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
Copyright 2025 The Silkworm Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#include "history_put_query.hpp"

#include <functional>

#include <catch2/catch_template_test_macros.hpp>
#include <catch2/catch_test_macros.hpp>

#include <silkworm/infra/common/directories.hpp>

#include "big_endian_codec.hpp"
#include "database.hpp"
#include "history_get_query.hpp"

namespace silkworm::datastore::kvdb {

struct HistoryPutEntry {
uint64_t key{0};
uint64_t value{0};
Timestamp timestamp{0};
};
using Entry = HistoryPutEntry;

using Result = tl::expected<uint64_t, HistoryGetQuery<BigEndianU64Codec, BigEndianU64Codec>::NoValueReason>;

// by default has_large_values = false, is_multi_value = true
using DomainDefault = std::identity;

struct DomainWithLargeValues {
Schema::DomainDef& operator()(Schema::DomainDef& domain) const {
domain.enable_large_values().values_disable_multi_value();
return domain;
}
};

TEMPLATE_TEST_CASE("HistoryPutQuery", "", DomainDefault, DomainWithLargeValues) {
const TemporaryDirectory tmp_dir;
::mdbx::env_managed env = open_env(EnvConfig{.path = tmp_dir.path().string(), .create = true, .in_memory = true});

EntityName name{"Test"};
Schema::DatabaseDef schema;
TestType domain_config;
[[maybe_unused]] auto _ = domain_config(schema.domain(name));

Database db{std::move(env), schema};
db.create_tables();
Domain domain = db.domain(name);
History& entity = *domain.history;
RWAccess db_access = db.access_rw();

auto find_in = [&db_access, &entity](const std::vector<Entry>& data, uint64_t key, Timestamp timestamp) -> Result {
{
RWTxnManaged tx = db_access.start_rw_tx();
HistoryPutQuery<BigEndianU64Codec, BigEndianU64Codec> query{tx, entity};
for (auto& entry : data) {
query.exec(entry.key, entry.value, entry.timestamp);
}
tx.commit_and_stop();
}

ROTxnManaged tx = db_access.start_ro_tx();
HistoryGetQuery<BigEndianU64Codec, BigEndianU64Codec> query{tx, entity};
return query.exec(key, timestamp);
};

SECTION("single entry - correct key") {
CHECK(find_in({Entry{1, 2, 3}}, 1, 3) == 2);
}
SECTION("single entry - wrong key") {
CHECK_FALSE(find_in({Entry{1, 2, 3}}, 4, 3).has_value());
}
SECTION("different timestamps - different keys") {
CHECK(find_in({Entry{1, 11, 101}, Entry{2, 22, 102}, Entry{3, 33, 103}}, 2, 102) == 22);
}
SECTION("same timestamp - different keys") {
CHECK(find_in({Entry{1, 11, 100}, Entry{2, 22, 100}, Entry{3, 33, 100}}, 2, 100) == 22);
}
SECTION("ascending timestamps - same key - before first") {
CHECK(find_in({Entry{1, 11, 101}, Entry{1, 33, 103}}, 1, 100) == 11);
}
SECTION("ascending timestamps - same key - first") {
CHECK(find_in({Entry{1, 11, 101}, Entry{1, 33, 103}}, 1, 101) == 11);
}
SECTION("ascending timestamps - same key - gap") {
CHECK(find_in({Entry{1, 11, 101}, Entry{1, 33, 103}}, 1, 102) == 33);
}
SECTION("ascending timestamps - same key - last") {
CHECK(find_in({Entry{1, 11, 101}, Entry{1, 33, 103}}, 1, 103) == 33);
}
SECTION("ascending timestamps - same key - after last") {
CHECK_FALSE(find_in({Entry{1, 11, 101}, Entry{1, 33, 103}}, 1, 104).has_value());
}
SECTION("descending timestamps - same key - before first") {
CHECK(find_in({Entry{1, 33, 103}, Entry{1, 11, 101}}, 1, 100) == 11);
}
SECTION("descending timestamps - same key - first") {
CHECK(find_in({Entry{1, 33, 103}, Entry{1, 11, 101}}, 1, 101) == 11);
}
SECTION("descending timestamps - same key - gap") {
CHECK(find_in({Entry{1, 33, 103}, Entry{1, 11, 101}}, 1, 102) == 33);
}
SECTION("descending timestamps - same key - last") {
CHECK(find_in({Entry{1, 33, 103}, Entry{1, 11, 101}}, 1, 103) == 33);
}
SECTION("descending timestamps - same key - after last") {
CHECK_FALSE(find_in({Entry{1, 33, 103}, Entry{1, 11, 101}}, 1, 104).has_value());
}
}

} // namespace silkworm::datastore::kvdb
1 change: 1 addition & 0 deletions silkworm/db/datastore/kvdb/history_queries.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@
#pragma once

#include "history_delete_query.hpp"
#include "history_get_query.hpp"
#include "history_put_query.hpp"

0 comments on commit 6606785

Please sign in to comment.