// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. // This source code is licensed under both the GPLv2 (found in the // COPYING file in the root directory) and Apache 2.0 License // (found in the LICENSE.Apache file in the root directory). // // Copyright (c) 2012 The LevelDB Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. See the AUTHORS file for names of contributors. #include "table/block_based_filter_block.h" #include "rocksdb/filter_policy.h" #include "util/coding.h" #include "util/hash.h" #include "util/string_util.h" #include "util/testharness.h" #include "util/testutil.h" namespace rocksdb { // For testing: emit an array with one hash value per key class TestHashFilter : public FilterPolicy { public: virtual const char* Name() const override { return "TestHashFilter"; } virtual void CreateFilter(const Slice* keys, int n, std::string* dst) const override { for (int i = 0; i < n; i++) { uint32_t h = Hash(keys[i].data(), keys[i].size(), 1); PutFixed32(dst, h); } } virtual bool KeyMayMatch(const Slice& key, const Slice& filter) const override { uint32_t h = Hash(key.data(), key.size(), 1); for (unsigned int i = 0; i + 4 <= filter.size(); i += 4) { if (h == DecodeFixed32(filter.data() + i)) { return true; } } return false; } }; class FilterBlockTest : public testing::Test { public: TestHashFilter policy_; BlockBasedTableOptions table_options_; FilterBlockTest() { table_options_.filter_policy.reset(new TestHashFilter()); } }; TEST_F(FilterBlockTest, EmptyBuilder) { BlockBasedFilterBlockBuilder builder(nullptr, table_options_); BlockContents block(builder.Finish(), false, kNoCompression); ASSERT_EQ("\\x00\\x00\\x00\\x00\\x0b", EscapeString(block.data)); BlockBasedFilterBlockReader reader(nullptr, table_options_, true, std::move(block), nullptr); ASSERT_TRUE(reader.KeyMayMatch("foo", 0)); ASSERT_TRUE(reader.KeyMayMatch("foo", 100000)); } TEST_F(FilterBlockTest, SingleChunk) { BlockBasedFilterBlockBuilder builder(nullptr, table_options_); builder.StartBlock(100); builder.Add("foo"); builder.Add("bar"); builder.Add("box"); builder.StartBlock(200); builder.Add("box"); builder.StartBlock(300); builder.Add("hello"); BlockContents block(builder.Finish(), false, kNoCompression); BlockBasedFilterBlockReader reader(nullptr, table_options_, true, std::move(block), nullptr); ASSERT_TRUE(reader.KeyMayMatch("foo", 100)); ASSERT_TRUE(reader.KeyMayMatch("bar", 100)); ASSERT_TRUE(reader.KeyMayMatch("box", 100)); ASSERT_TRUE(reader.KeyMayMatch("hello", 100)); ASSERT_TRUE(reader.KeyMayMatch("foo", 100)); ASSERT_TRUE(!reader.KeyMayMatch("missing", 100)); ASSERT_TRUE(!reader.KeyMayMatch("other", 100)); } TEST_F(FilterBlockTest, MultiChunk) { BlockBasedFilterBlockBuilder builder(nullptr, table_options_); // First filter builder.StartBlock(0); builder.Add("foo"); builder.StartBlock(2000); builder.Add("bar"); // Second filter builder.StartBlock(3100); builder.Add("box"); // Third filter is empty // Last filter builder.StartBlock(9000); builder.Add("box"); builder.Add("hello"); BlockContents block(builder.Finish(), false, kNoCompression); BlockBasedFilterBlockReader reader(nullptr, table_options_, true, std::move(block), nullptr); // Check first filter ASSERT_TRUE(reader.KeyMayMatch("foo", 0)); ASSERT_TRUE(reader.KeyMayMatch("bar", 2000)); ASSERT_TRUE(!reader.KeyMayMatch("box", 0)); ASSERT_TRUE(!reader.KeyMayMatch("hello", 0)); // Check second filter ASSERT_TRUE(reader.KeyMayMatch("box", 3100)); ASSERT_TRUE(!reader.KeyMayMatch("foo", 3100)); ASSERT_TRUE(!reader.KeyMayMatch("bar", 3100)); ASSERT_TRUE(!reader.KeyMayMatch("hello", 3100)); // Check third filter (empty) ASSERT_TRUE(!reader.KeyMayMatch("foo", 4100)); ASSERT_TRUE(!reader.KeyMayMatch("bar", 4100)); ASSERT_TRUE(!reader.KeyMayMatch("box", 4100)); ASSERT_TRUE(!reader.KeyMayMatch("hello", 4100)); // Check last filter ASSERT_TRUE(reader.KeyMayMatch("box", 9000)); ASSERT_TRUE(reader.KeyMayMatch("hello", 9000)); ASSERT_TRUE(!reader.KeyMayMatch("foo", 9000)); ASSERT_TRUE(!reader.KeyMayMatch("bar", 9000)); } // Test for block based filter block // use new interface in FilterPolicy to create filter builder/reader class BlockBasedFilterBlockTest : public testing::Test { public: BlockBasedTableOptions table_options_; BlockBasedFilterBlockTest() { table_options_.filter_policy.reset(NewBloomFilterPolicy(10)); } ~BlockBasedFilterBlockTest() {} }; TEST_F(BlockBasedFilterBlockTest, BlockBasedEmptyBuilder) { FilterBlockBuilder* builder = new BlockBasedFilterBlockBuilder( nullptr, table_options_); BlockContents block(builder->Finish(), false, kNoCompression); ASSERT_EQ("\\x00\\x00\\x00\\x00\\x0b", EscapeString(block.data)); FilterBlockReader* reader = new BlockBasedFilterBlockReader( nullptr, table_options_, true, std::move(block), nullptr); ASSERT_TRUE(reader->KeyMayMatch("foo", 0)); ASSERT_TRUE(reader->KeyMayMatch("foo", 100000)); delete builder; delete reader; } TEST_F(BlockBasedFilterBlockTest, BlockBasedSingleChunk) { FilterBlockBuilder* builder = new BlockBasedFilterBlockBuilder( nullptr, table_options_); builder->StartBlock(100); builder->Add("foo"); builder->Add("bar"); builder->Add("box"); builder->StartBlock(200); builder->Add("box"); builder->StartBlock(300); builder->Add("hello"); BlockContents block(builder->Finish(), false, kNoCompression); FilterBlockReader* reader = new BlockBasedFilterBlockReader( nullptr, table_options_, true, std::move(block), nullptr); ASSERT_TRUE(reader->KeyMayMatch("foo", 100)); ASSERT_TRUE(reader->KeyMayMatch("bar", 100)); ASSERT_TRUE(reader->KeyMayMatch("box", 100)); ASSERT_TRUE(reader->KeyMayMatch("hello", 100)); ASSERT_TRUE(reader->KeyMayMatch("foo", 100)); ASSERT_TRUE(!reader->KeyMayMatch("missing", 100)); ASSERT_TRUE(!reader->KeyMayMatch("other", 100)); delete builder; delete reader; } TEST_F(BlockBasedFilterBlockTest, BlockBasedMultiChunk) { FilterBlockBuilder* builder = new BlockBasedFilterBlockBuilder( nullptr, table_options_); // First filter builder->StartBlock(0); builder->Add("foo"); builder->StartBlock(2000); builder->Add("bar"); // Second filter builder->StartBlock(3100); builder->Add("box"); // Third filter is empty // Last filter builder->StartBlock(9000); builder->Add("box"); builder->Add("hello"); BlockContents block(builder->Finish(), false, kNoCompression); FilterBlockReader* reader = new BlockBasedFilterBlockReader( nullptr, table_options_, true, std::move(block), nullptr); // Check first filter ASSERT_TRUE(reader->KeyMayMatch("foo", 0)); ASSERT_TRUE(reader->KeyMayMatch("bar", 2000)); ASSERT_TRUE(!reader->KeyMayMatch("box", 0)); ASSERT_TRUE(!reader->KeyMayMatch("hello", 0)); // Check second filter ASSERT_TRUE(reader->KeyMayMatch("box", 3100)); ASSERT_TRUE(!reader->KeyMayMatch("foo", 3100)); ASSERT_TRUE(!reader->KeyMayMatch("bar", 3100)); ASSERT_TRUE(!reader->KeyMayMatch("hello", 3100)); // Check third filter (empty) ASSERT_TRUE(!reader->KeyMayMatch("foo", 4100)); ASSERT_TRUE(!reader->KeyMayMatch("bar", 4100)); ASSERT_TRUE(!reader->KeyMayMatch("box", 4100)); ASSERT_TRUE(!reader->KeyMayMatch("hello", 4100)); // Check last filter ASSERT_TRUE(reader->KeyMayMatch("box", 9000)); ASSERT_TRUE(reader->KeyMayMatch("hello", 9000)); ASSERT_TRUE(!reader->KeyMayMatch("foo", 9000)); ASSERT_TRUE(!reader->KeyMayMatch("bar", 9000)); delete builder; delete reader; } } // namespace rocksdb int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }