#include "rocksdb/iostats_context.h"
#include "rocksdb/perf_context.h"
#include "table/block_based/flush_block_policy.h"
+#include "util/random.h"
namespace ROCKSDB_NAMESPACE {
class DBIteratorTest : public DBTestBase,
public testing::WithParamInterface<bool> {
public:
- DBIteratorTest() : DBTestBase("/db_iterator_test") {}
+ DBIteratorTest() : DBTestBase("/db_iterator_test", /*env_do_fsync=*/true) {}
Iterator* NewIterator(const ReadOptions& read_options,
ColumnFamilyHandle* column_family = nullptr) {
if (column_family == nullptr) {
column_family = db_->DefaultColumnFamily();
}
- auto* cfd = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family)->cfd();
+ auto* cfd =
+ static_cast_with_check<ColumnFamilyHandleImpl>(column_family)->cfd();
SequenceNumber seq = read_options.snapshot != nullptr
? read_options.snapshot->GetSequenceNumber()
: db_->GetLatestSequenceNumber();
options.compression = kNoCompression;
Reopen(options);
- ASSERT_OK(Put("a", RandomString(&rnd, 400)));
- ASSERT_OK(Put("aabb", RandomString(&rnd, 400)));
- ASSERT_OK(Put("aaef", RandomString(&rnd, 400)));
- ASSERT_OK(Put("b", RandomString(&rnd, 400)));
+ ASSERT_OK(Put("a", rnd.RandomString(400)));
+ ASSERT_OK(Put("aabb", rnd.RandomString(400)));
+ ASSERT_OK(Put("aaef", rnd.RandomString(400)));
+ ASSERT_OK(Put("b", rnd.RandomString(400)));
dbfull()->Flush(FlushOptions());
ReadOptions opts;
Slice ub = Slice("aa");
ropt.tailing = tailing;
std::unique_ptr<Iterator> iter(NewIterator(ropt));
+ ropt.read_tier = ReadTier::kBlockCacheTier;
+ std::unique_ptr<Iterator> nonblocking_iter(NewIterator(ropt));
+
iter->Seek("b10");
ASSERT_TRUE(iter->Valid());
EXPECT_EQ("b2", iter->key().ToString());
EXPECT_EQ("y2", iter->value().ToString());
EXPECT_EQ(1, stats->getTickerCount(BLOCK_CACHE_DATA_MISS));
+ // The cache-only iterator should succeed too, using the blocks pulled into
+ // the cache by the previous iterator.
+ nonblocking_iter->Seek("b10");
+ ASSERT_TRUE(nonblocking_iter->Valid());
+ EXPECT_EQ("b2", nonblocking_iter->key().ToString());
+ EXPECT_EQ("y2", nonblocking_iter->value().ToString());
+ EXPECT_EQ(1, stats->getTickerCount(BLOCK_CACHE_DATA_HIT));
+
+ // ... but it shouldn't be able to step forward since the next block is
+ // not in cache yet.
+ nonblocking_iter->Next();
+ ASSERT_FALSE(nonblocking_iter->Valid());
+ ASSERT_TRUE(nonblocking_iter->status().IsIncomplete());
+
+ // ... nor should a seek to the next key succeed.
+ nonblocking_iter->Seek("b20");
+ ASSERT_FALSE(nonblocking_iter->Valid());
+ ASSERT_TRUE(nonblocking_iter->status().IsIncomplete());
+
iter->Next();
ASSERT_TRUE(iter->Valid());
EXPECT_EQ("b3", iter->key().ToString());
EXPECT_EQ("y3", iter->value().ToString());
- EXPECT_EQ(2, stats->getTickerCount(BLOCK_CACHE_DATA_MISS));
- EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT));
+ EXPECT_EQ(4, stats->getTickerCount(BLOCK_CACHE_DATA_MISS));
+ EXPECT_EQ(1, stats->getTickerCount(BLOCK_CACHE_DATA_HIT));
+
+ // After the blocking iterator loaded the next block, the nonblocking
+ // iterator's seek should succeed.
+ nonblocking_iter->Seek("b20");
+ ASSERT_TRUE(nonblocking_iter->Valid());
+ EXPECT_EQ("b3", nonblocking_iter->key().ToString());
+ EXPECT_EQ("y3", nonblocking_iter->value().ToString());
+ EXPECT_EQ(2, stats->getTickerCount(BLOCK_CACHE_DATA_HIT));
iter->Seek("c0");
ASSERT_TRUE(iter->Valid());
EXPECT_EQ("c0", iter->key().ToString());
EXPECT_EQ("z1,z2", iter->value().ToString());
- EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT));
- EXPECT_EQ(4, stats->getTickerCount(BLOCK_CACHE_DATA_MISS));
+ EXPECT_EQ(2, stats->getTickerCount(BLOCK_CACHE_DATA_HIT));
+ EXPECT_EQ(6, stats->getTickerCount(BLOCK_CACHE_DATA_MISS));
iter->Next();
ASSERT_TRUE(iter->Valid());
EXPECT_EQ("c3", iter->key().ToString());
EXPECT_EQ("z3", iter->value().ToString());
- EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT));
- EXPECT_EQ(5, stats->getTickerCount(BLOCK_CACHE_DATA_MISS));
+ EXPECT_EQ(2, stats->getTickerCount(BLOCK_CACHE_DATA_HIT));
+ EXPECT_EQ(7, stats->getTickerCount(BLOCK_CACHE_DATA_MISS));
iter.reset();
ASSERT_TRUE(iter->Valid());
EXPECT_EQ("b2", iter->key().ToString());
EXPECT_EQ("y2", iter->value().ToString());
- EXPECT_EQ(1, stats->getTickerCount(BLOCK_CACHE_DATA_HIT));
- EXPECT_EQ(5, stats->getTickerCount(BLOCK_CACHE_DATA_MISS));
+ EXPECT_EQ(3, stats->getTickerCount(BLOCK_CACHE_DATA_HIT));
+ EXPECT_EQ(7, stats->getTickerCount(BLOCK_CACHE_DATA_MISS));
iter->Next();
ASSERT_FALSE(iter->Valid());
- EXPECT_EQ(1, stats->getTickerCount(BLOCK_CACHE_DATA_HIT));
- EXPECT_EQ(5, stats->getTickerCount(BLOCK_CACHE_DATA_MISS));
+ EXPECT_EQ(3, stats->getTickerCount(BLOCK_CACHE_DATA_HIT));
+ EXPECT_EQ(7, stats->getTickerCount(BLOCK_CACHE_DATA_MISS));
}
}
std::vector<std::string> generated_keys(key_pool);
for (int i = 0; i < key_pool; i++) {
- generated_keys[i] = RandomString(&rnd, key_size);
+ generated_keys[i] = rnd.RandomString(key_size);
}
std::map<std::string, std::string> true_data;
std::vector<std::string> deleted_keys;
for (int i = 0; i < puts; i++) {
auto& k = generated_keys[rnd.Next() % key_pool];
- auto v = RandomString(&rnd, val_size);
+ auto v = rnd.RandomString(val_size);
// Insert data to true_data map and to DB
true_data[k] = v;
Random rnd(301);
for (int i = 1; i <= 1000; i++) {
std::string k = Key(i * 3);
- std::string v = RandomString(&rnd, 100);
+ std::string v = rnd.RandomString(100);
ASSERT_OK(Put(k, v));
true_data[k] = v;
if (i % 250 == 0) {
// Generate 4 sst files in L0
for (int i = 1; i <= 1000; i++) {
std::string k = Key(i * 2);
- std::string v = RandomString(&rnd, 100);
+ std::string v = rnd.RandomString(100);
ASSERT_OK(Put(k, v));
true_data[k] = v;
if (i % 250 == 0) {
// Add some keys/values in memtables
for (int i = 1; i <= 1000; i++) {
std::string k = Key(i);
- std::string v = RandomString(&rnd, 100);
+ std::string v = rnd.RandomString(100);
ASSERT_OK(Put(k, v));
true_data[k] = v;
}
std::map<std::string, std::string> true_data;
for (int i = 0; i < 1000; i++) {
- std::string k = RandomString(&rnd, 10);
- std::string v = RandomString(&rnd, 1000);
+ std::string k = rnd.RandomString(10);
+ std::string v = rnd.RandomString(1000);
ASSERT_OK(Put(k, v));
true_data[k] = v;
}
if (rnd.OneIn(2)) {
ASSERT_OK(Delete(kv.first));
} else {
- std::string new_val = RandomString(&rnd, 1000);
+ std::string new_val = rnd.RandomString(1000);
ASSERT_OK(Put(kv.first, new_val));
}
}
DestroyAndReopen(options);
const int kNumKeys = 500;
- // Small number of merge operands to make sure that DBIter::Prev() dont
+ // Small number of merge operands to make sure that DBIter::Prev() don't
// fall back to Seek()
const int kNumMergeOperands = 3;
// Use value size that will make sure that every block contain 1 key
for (int i = 0; i < kNumKeys; i++) {
gen_key = Key(i);
- gen_val = RandomString(&rnd, kValSize);
+ gen_val = rnd.RandomString(kValSize);
ASSERT_OK(Put(gen_key, gen_val));
true_data[gen_key] = gen_val;
ASSERT_OK(Flush());
// Separate values and merge operands in different file so that we
- // make sure that we dont merge them while flushing but actually
+ // make sure that we don't merge them while flushing but actually
// merge them in the read path
for (int i = 0; i < kNumKeys; i++) {
if (rnd.PercentTrue(kNoMergeOpPercentage)) {
for (int j = 0; j < kNumMergeOperands; j++) {
gen_key = Key(i);
- gen_val = RandomString(&rnd, kValSize);
+ gen_val = rnd.RandomString(kValSize);
ASSERT_OK(db_->Merge(WriteOptions(), gen_key, gen_val));
true_data[gen_key] += "," + gen_val;
Random rnd(301);
for (int i = 0; i < 1000; i++) {
// Key 10 bytes / Value 10 bytes
- ASSERT_OK(Put(RandomString(&rnd, 10), RandomString(&rnd, 10)));
+ ASSERT_OK(Put(rnd.RandomString(10), rnd.RandomString(10)));
}
std::atomic<uint64_t> total_next(0);
BlockBasedTableOptions table_options;
table_options.block_size = 1024;
table_options.no_block_cache = true;
- options.table_factory.reset(new BlockBasedTableFactory(table_options));
+ options.table_factory.reset(NewBlockBasedTableFactory(table_options));
Reopen(options);
std::string value(1024, 'a');
Reopen(options);
Random rnd(301);
- std::string random_str = RandomString(&rnd, 180);
+ std::string random_str = rnd.RandomString(180);
ASSERT_OK(Put("1", random_str));
ASSERT_OK(Put("2", random_str));
SequenceNumber seq2 = db_->GetLatestSequenceNumber();
auto* cfd =
- reinterpret_cast<ColumnFamilyHandleImpl*>(db_->DefaultColumnFamily())
+ static_cast_with_check<ColumnFamilyHandleImpl>(db_->DefaultColumnFamily())
->cfd();
// The iterator are suppose to see data before seq1.
Iterator* iter =
delete iter;
}
+TEST_F(DBIteratorTest, BackwardIterationOnInplaceUpdateMemtable) {
+ Options options = CurrentOptions();
+ options.create_if_missing = true;
+ options.inplace_update_support = false;
+ options.env = env_;
+ DestroyAndReopen(options);
+ constexpr int kNumKeys = 10;
+
+ // Write kNumKeys to WAL.
+ for (int i = 0; i < kNumKeys; ++i) {
+ ASSERT_OK(Put(Key(i), "val"));
+ }
+ ReadOptions read_opts;
+ read_opts.total_order_seek = true;
+ {
+ std::unique_ptr<Iterator> iter(db_->NewIterator(read_opts));
+ int count = 0;
+ for (iter->SeekToLast(); iter->Valid(); iter->Prev()) {
+ ++count;
+ }
+ ASSERT_EQ(kNumKeys, count);
+ }
+
+ // Reopen and rebuild the memtable from WAL.
+ options.create_if_missing = false;
+ options.avoid_flush_during_recovery = true;
+ options.inplace_update_support = true;
+ options.allow_concurrent_memtable_write = false;
+ Reopen(options);
+ {
+ std::unique_ptr<Iterator> iter(db_->NewIterator(read_opts));
+ iter->SeekToLast();
+ // Backward iteration not supported due to inplace_update_support = true.
+ ASSERT_TRUE(iter->status().IsNotSupported());
+ ASSERT_FALSE(iter->Valid());
+ }
+}
+
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {