diff --git a/db/db_with_timestamp_basic_test.cc b/db/db_with_timestamp_basic_test.cc index 9f29da07b..e4146858e 100644 --- a/db/db_with_timestamp_basic_test.cc +++ b/db/db_with_timestamp_basic_test.cc @@ -595,6 +595,41 @@ TEST_F(DBBasicTestWithTimestamp, SimpleIterate) { Close(); } +#ifndef ROCKSDB_LITE +TEST_F(DBBasicTestWithTimestamp, GetTimestampTableProperties) { + Options options = CurrentOptions(); + const size_t kTimestampSize = Timestamp(0, 0).size(); + TestComparator test_cmp(kTimestampSize); + options.comparator = &test_cmp; + DestroyAndReopen(options); + // Create 2 tables + for (int table = 0; table < 2; ++table) { + for (int i = 0; i < 10; i++) { + WriteOptions write_opts; + std::string ts_str = Timestamp(i, 0); + Slice ts = ts_str; + write_opts.timestamp = &ts; + ASSERT_OK(db_->Put(write_opts, "key", Key(i))); + } + ASSERT_OK(Flush()); + } + + TablePropertiesCollection props; + ASSERT_OK(db_->GetPropertiesOfAllTables(&props)); + ASSERT_EQ(2U, props.size()); + for (const auto& item : props) { + auto& user_collected = item.second->user_collected_properties; + ASSERT_TRUE(user_collected.find("rocksdb.timestamp_min") != + user_collected.end()); + ASSERT_TRUE(user_collected.find("rocksdb.timestamp_max") != + user_collected.end()); + ASSERT_EQ(user_collected.at("rocksdb.timestamp_min"), Timestamp(0, 0)); + ASSERT_EQ(user_collected.at("rocksdb.timestamp_max"), Timestamp(9, 0)); + } + Close(); +} +#endif // !ROCKSDB_LITE + class DBBasicTestWithTimestampTableOptions : public DBBasicTestWithTimestampBase, public testing::WithParamInterface { diff --git a/db/table_properties_collector.h b/db/table_properties_collector.h index 5b3d5b56c..2a73f907d 100644 --- a/db/table_properties_collector.h +++ b/db/table_properties_collector.h @@ -6,12 +6,14 @@ // This file defines a collection of statistics collectors. #pragma once -#include "rocksdb/table_properties.h" - #include #include #include +#include "db/dbformat.h" +#include "rocksdb/comparator.h" +#include "rocksdb/table_properties.h" + namespace ROCKSDB_NAMESPACE { // Base class for internal table properties collector. @@ -108,4 +110,66 @@ class UserKeyTablePropertiesCollectorFactory std::shared_ptr user_collector_factory_; }; +// When rocksdb creates a newtable, it will encode all "user keys" into +// "internal keys". This class collects min/max timestamp from the encoded +// internal key when Add() is invoked. +// +// @param cmp the user comparator to compare the timestamps in internal key. +class TimestampTablePropertiesCollector : public IntTblPropCollector { + public: + explicit TimestampTablePropertiesCollector(const Comparator* cmp) + : cmp_(cmp), + timestamp_min_(kDisableUserTimestamp), + timestamp_max_(kDisableUserTimestamp) {} + + Status InternalAdd(const Slice& key, const Slice& /* value */, + uint64_t /* file_size */) override { + auto user_key = ExtractUserKey(key); + assert(cmp_ && cmp_->timestamp_size() > 0); + if (user_key.size() < cmp_->timestamp_size()) { + return Status::Corruption( + "User key size mismatch when comparing to timestamp size."); + } + auto timestamp_in_key = + ExtractTimestampFromUserKey(user_key, cmp_->timestamp_size()); + if (timestamp_max_ == kDisableUserTimestamp || + cmp_->CompareTimestamp(timestamp_in_key, timestamp_max_) > 0) { + timestamp_max_.assign(timestamp_in_key.data(), timestamp_in_key.size()); + } + if (timestamp_min_ == kDisableUserTimestamp || + cmp_->CompareTimestamp(timestamp_min_, timestamp_in_key) > 0) { + timestamp_min_.assign(timestamp_in_key.data(), timestamp_in_key.size()); + } + return Status::OK(); + } + + void BlockAdd(uint64_t /* block_raw_bytes */, + uint64_t /* block_compressed_bytes_fast */, + uint64_t /* block_compressed_bytes_slow */) override { + return; + } + + Status Finish(UserCollectedProperties* properties) override { + assert(timestamp_min_.size() == timestamp_max_.size() && + timestamp_max_.size() == cmp_->timestamp_size()); + properties->insert({"rocksdb.timestamp_min", timestamp_min_}); + properties->insert({"rocksdb.timestamp_max", timestamp_max_}); + return Status::OK(); + } + + const char* Name() const override { + return "TimestampTablePropertiesCollector"; + } + + UserCollectedProperties GetReadableProperties() const override { + return {{"rocksdb.timestamp_min", Slice(timestamp_min_).ToString(true)}, + {"rocksdb.timestamp_max", Slice(timestamp_max_).ToString(true)}}; + } + + protected: + const Comparator* const cmp_; + std::string timestamp_min_; + std::string timestamp_max_; +}; + } // namespace ROCKSDB_NAMESPACE diff --git a/table/block_based/block_based_table_builder.cc b/table/block_based/block_based_table_builder.cc index 92e9cb9ba..f7b272154 100644 --- a/table/block_based/block_based_table_builder.cc +++ b/table/block_based/block_based_table_builder.cc @@ -517,6 +517,12 @@ struct BlockBasedTableBuilder::Rep { new BlockBasedTablePropertiesCollector( table_options.index_type, table_options.whole_key_filtering, moptions.prefix_extractor != nullptr)); + const Comparator* ucmp = tbo.internal_comparator.user_comparator(); + assert(ucmp); + if (ucmp->timestamp_size() > 0) { + table_properties_collectors.emplace_back( + new TimestampTablePropertiesCollector(ucmp)); + } if (table_options.verify_compression) { for (uint32_t i = 0; i < compression_opts.parallel_threads; i++) { verify_ctxs[i].reset(new UncompressionContext(compression_type));