ASSERT_OK(iter->Refresh());
}
+TEST_F(DBRangeDelTest, RangeTombstoneRespectIterateUpperBound) {
+ // Memtable: a, [b, bz)
+ // Do a Seek on `a` with iterate_upper_bound being az
+ // range tombstone [b, bz) should not be processed (added to and
+ // popped from the min_heap in MergingIterator).
+ Options options = CurrentOptions();
+ options.disable_auto_compactions = true;
+ DestroyAndReopen(options);
+
+ ASSERT_OK(Put("a", "bar"));
+ ASSERT_OK(
+ db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "b", "bz"));
+
+ // I could not find a cleaner way to test this without relying on
+ // implementation detail. Tried to test the value of
+ // `internal_range_del_reseek_count` but that did not work
+ // since BlockBasedTable iterator becomes !Valid() when point key
+ // is out of bound and that reseek only happens when a point key
+ // is covered by some range tombstone.
+ SyncPoint::GetInstance()->SetCallBack("MergeIterator::PopDeleteRangeStart",
+ [](void*) {
+ // there should not be any range
+ // tombstone in the heap.
+ FAIL();
+ });
+ SyncPoint::GetInstance()->EnableProcessing();
+
+ ReadOptions read_opts;
+ std::string upper_bound = "az";
+ Slice upper_bound_slice = upper_bound;
+ read_opts.iterate_upper_bound = &upper_bound_slice;
+ std::unique_ptr<Iterator> iter{db_->NewIterator(read_opts)};
+ iter->Seek("a");
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ(iter->key(), "a");
+ iter->Next();
+ ASSERT_FALSE(iter->Valid());
+ ASSERT_OK(iter->status());
+}
+
#endif // ROCKSDB_LITE
} // namespace ROCKSDB_NAMESPACE
public:
MergingIterator(const InternalKeyComparator* comparator,
InternalIterator** children, int n, bool is_arena_mode,
- bool prefix_seek_mode)
+ bool prefix_seek_mode,
+ const Slice* iterate_upper_bound = nullptr)
: is_arena_mode_(is_arena_mode),
prefix_seek_mode_(prefix_seek_mode),
direction_(kForward),
comparator_(comparator),
current_(nullptr),
minHeap_(comparator_),
- pinned_iters_mgr_(nullptr) {
+ pinned_iters_mgr_(nullptr),
+ iterate_upper_bound_(iterate_upper_bound) {
children_.resize(n);
for (int i = 0; i < n; i++) {
children_[i].level = i;
assert(!range_tombstone_iters_.empty() &&
range_tombstone_iters_[level]->Valid());
if (start_key) {
- pinned_heap_item_[level].SetTombstoneKey(
- range_tombstone_iters_[level]->start_key());
+ ParsedInternalKey pik = range_tombstone_iters_[level]->start_key();
+ // iterate_upper_bound does not have timestamp
+ if (iterate_upper_bound_ &&
+ comparator_->user_comparator()->CompareWithoutTimestamp(
+ pik.user_key, true /* a_has_ts */, *iterate_upper_bound_,
+ false /* b_has_ts */) >= 0) {
+ if (replace_top) {
+ // replace_top implies this range tombstone iterator is still in
+ // minHeap_ and at the top.
+ minHeap_.pop();
+ }
+ return;
+ }
+ pinned_heap_item_[level].SetTombstoneKey(std::move(pik));
pinned_heap_item_[level].type = HeapItem::DELETE_RANGE_START;
assert(active_.count(level) == 0);
} else {
+ // allow end key to go over upper bound (if present) since start key is
+ // before upper bound and the range tombstone could still cover a
+ // range before upper bound.
pinned_heap_item_[level].SetTombstoneKey(
range_tombstone_iters_[level]->end_key());
pinned_heap_item_[level].type = HeapItem::DELETE_RANGE_END;
void PopDeleteRangeStart() {
while (!minHeap_.empty() &&
minHeap_.top()->type == HeapItem::DELETE_RANGE_START) {
+ TEST_SYNC_POINT_CALLBACK("MergeIterator::PopDeleteRangeStart", nullptr);
// insert end key of this range tombstone and updates active_
InsertRangeTombstoneToMinHeap(
minHeap_.top()->level, false /* start_key */, true /* replace_top */);
std::unique_ptr<MergerMaxIterHeap> maxHeap_;
PinnedIteratorsManager* pinned_iters_mgr_;
+ // Used to bound range tombstones. For point keys, DBIter and SSTable iterator
+ // take care of boundary checking.
+ const Slice* iterate_upper_bound_;
+
// In forward direction, process a child that is not in the min heap.
// If valid, add to the min heap. Otherwise, check status.
void AddToMinHeapOrCheckStatus(HeapItem*);
for (size_t level = 0; level < starting_level; ++level) {
if (range_tombstone_iters_[level] &&
range_tombstone_iters_[level]->Valid()) {
- assert(static_cast<bool>(active_.count(level)) ==
- (pinned_heap_item_[level].type == HeapItem::DELETE_RANGE_END));
- minHeap_.push(&pinned_heap_item_[level]);
+ // use an iterator on active_ if performance becomes an issue here
+ if (active_.count(level) > 0) {
+ assert(pinned_heap_item_[level].type == HeapItem::DELETE_RANGE_END);
+ // if it was active, then start key must be within upper_bound,
+ // so we can add to minHeap_ directly.
+ minHeap_.push(&pinned_heap_item_[level]);
+ } else {
+ // this takes care of checking iterate_upper_bound, but with an extra
+ // key comparison if range_tombstone_iters_[level] was already out of
+ // bound. Consider using a new HeapItem type or some flag to remember
+ // boundary checking result.
+ InsertRangeTombstoneToMinHeap(level);
+ }
} else {
assert(!active_.count(level));
}
}
MergeIteratorBuilder::MergeIteratorBuilder(
- const InternalKeyComparator* comparator, Arena* a, bool prefix_seek_mode)
+ const InternalKeyComparator* comparator, Arena* a, bool prefix_seek_mode,
+ const Slice* iterate_upper_bound)
: first_iter(nullptr), use_merging_iter(false), arena(a) {
auto mem = arena->AllocateAligned(sizeof(MergingIterator));
- merge_iter =
- new (mem) MergingIterator(comparator, nullptr, 0, true, prefix_seek_mode);
+ merge_iter = new (mem) MergingIterator(comparator, nullptr, 0, true,
+ prefix_seek_mode, iterate_upper_bound);
}
MergeIteratorBuilder::~MergeIteratorBuilder() {