1 // Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
2 // This source code is licensed under both the GPLv2 (found in the
3 // COPYING file in the root directory) and Apache 2.0 License
4 // (found in the LICENSE.Apache file in the root directory).
8 #include "rocksdb/utilities/write_batch_with_index.h"
12 #include "db/column_family.h"
13 #include "db/db_impl/db_impl.h"
14 #include "db/merge_context.h"
15 #include "db/merge_helper.h"
16 #include "memory/arena.h"
17 #include "memtable/skiplist.h"
18 #include "options/db_options.h"
19 #include "rocksdb/comparator.h"
20 #include "rocksdb/iterator.h"
21 #include "util/cast_util.h"
22 #include "util/string_util.h"
23 #include "utilities/write_batch_with_index/write_batch_with_index_internal.h"
25 namespace ROCKSDB_NAMESPACE
{
26 struct WriteBatchWithIndex::Rep
{
27 explicit Rep(const Comparator
* index_comparator
, size_t reserved_bytes
= 0,
28 size_t max_bytes
= 0, bool _overwrite_key
= false,
29 size_t protection_bytes_per_key
= 0)
30 : write_batch(reserved_bytes
, max_bytes
, protection_bytes_per_key
,
31 index_comparator
? index_comparator
->timestamp_size() : 0),
32 comparator(index_comparator
, &write_batch
),
33 skip_list(comparator
, &arena
),
34 overwrite_key(_overwrite_key
),
36 last_sub_batch_offset(0),
38 ReadableWriteBatch write_batch
;
39 WriteBatchEntryComparator comparator
;
41 WriteBatchEntrySkipList skip_list
;
43 size_t last_entry_offset
;
44 // The starting offset of the last sub-batch. A sub-batch starts right before
45 // inserting a key that is a duplicate of a key in the last sub-batch. Zero,
46 // the default, means that no duplicate key is detected so far.
47 size_t last_sub_batch_offset
;
48 // Total number of sub-batches in the write batch. Default is 1.
51 // Remember current offset of internal write batch, which is used as
52 // the starting offset of the next record.
53 void SetLastEntryOffset() { last_entry_offset
= write_batch
.GetDataSize(); }
55 // In overwrite mode, find the existing entry for the same key and update it
56 // to point to the current entry.
57 // Return true if the key is found and updated.
58 bool UpdateExistingEntry(ColumnFamilyHandle
* column_family
, const Slice
& key
,
60 bool UpdateExistingEntryWithCfId(uint32_t column_family_id
, const Slice
& key
,
63 // Add the recent entry to the update.
64 // In overwrite mode, if key already exists in the index, update it.
65 void AddOrUpdateIndex(ColumnFamilyHandle
* column_family
, const Slice
& key
,
67 void AddOrUpdateIndex(const Slice
& key
, WriteType type
);
69 // Allocate an index entry pointing to the last entry in the write batch and
70 // put it to skip list.
71 void AddNewEntry(uint32_t column_family_id
);
73 // Clear all updates buffered in this batch.
77 // Rebuild index by reading all records from the batch.
78 // Returns non-ok status on corruption.
79 Status
ReBuildIndex();
82 bool WriteBatchWithIndex::Rep::UpdateExistingEntry(
83 ColumnFamilyHandle
* column_family
, const Slice
& key
, WriteType type
) {
84 uint32_t cf_id
= GetColumnFamilyID(column_family
);
85 return UpdateExistingEntryWithCfId(cf_id
, key
, type
);
88 bool WriteBatchWithIndex::Rep::UpdateExistingEntryWithCfId(
89 uint32_t column_family_id
, const Slice
& key
, WriteType type
) {
94 WBWIIteratorImpl
iter(column_family_id
, &skip_list
, &write_batch
,
99 } else if (!iter
.MatchesKey(column_family_id
, key
)) {
102 // Move to the end of this key (NextKey-Prev)
103 iter
.NextKey(); // Move to the next key
105 iter
.Prev(); // Move back one entry
110 WriteBatchIndexEntry
* non_const_entry
=
111 const_cast<WriteBatchIndexEntry
*>(iter
.GetRawEntry());
112 if (LIKELY(last_sub_batch_offset
<= non_const_entry
->offset
)) {
113 last_sub_batch_offset
= last_entry_offset
;
116 if (type
== kMergeRecord
) {
119 non_const_entry
->offset
= last_entry_offset
;
124 void WriteBatchWithIndex::Rep::AddOrUpdateIndex(
125 ColumnFamilyHandle
* column_family
, const Slice
& key
, WriteType type
) {
126 if (!UpdateExistingEntry(column_family
, key
, type
)) {
127 uint32_t cf_id
= GetColumnFamilyID(column_family
);
128 const auto* cf_cmp
= GetColumnFamilyUserComparator(column_family
);
129 if (cf_cmp
!= nullptr) {
130 comparator
.SetComparatorForCF(cf_id
, cf_cmp
);
136 void WriteBatchWithIndex::Rep::AddOrUpdateIndex(const Slice
& key
,
138 if (!UpdateExistingEntryWithCfId(0, key
, type
)) {
143 void WriteBatchWithIndex::Rep::AddNewEntry(uint32_t column_family_id
) {
144 const std::string
& wb_data
= write_batch
.Data();
145 Slice entry_ptr
= Slice(wb_data
.data() + last_entry_offset
,
146 wb_data
.size() - last_entry_offset
);
150 ReadKeyFromWriteBatchEntry(&entry_ptr
, &key
, column_family_id
!= 0);
156 const Comparator
* const ucmp
= comparator
.GetComparator(column_family_id
);
157 size_t ts_sz
= ucmp
? ucmp
->timestamp_size() : 0;
160 key
.remove_suffix(ts_sz
);
163 auto* mem
= arena
.Allocate(sizeof(WriteBatchIndexEntry
));
165 new (mem
) WriteBatchIndexEntry(last_entry_offset
, column_family_id
,
166 key
.data() - wb_data
.data(), key
.size());
167 skip_list
.Insert(index_entry
);
170 void WriteBatchWithIndex::Rep::Clear() {
175 void WriteBatchWithIndex::Rep::ClearIndex() {
176 skip_list
.~WriteBatchEntrySkipList();
178 new (&arena
) Arena();
179 new (&skip_list
) WriteBatchEntrySkipList(comparator
, &arena
);
180 last_entry_offset
= 0;
181 last_sub_batch_offset
= 0;
185 Status
WriteBatchWithIndex::Rep::ReBuildIndex() {
190 if (write_batch
.Count() == 0) {
191 // Nothing to re-index
195 size_t offset
= WriteBatchInternal::GetFirstOffset(&write_batch
);
197 Slice
input(write_batch
.Data());
198 input
.remove_prefix(offset
);
200 // Loop through all entries in Rep and add each one to the index
202 while (s
.ok() && !input
.empty()) {
203 Slice key
, value
, blob
, xid
;
204 uint32_t column_family_id
= 0; // default
207 // set offset of current entry for call to AddNewEntry()
208 last_entry_offset
= input
.data() - write_batch
.Data().data();
210 s
= ReadRecordFromWriteBatch(&input
, &tag
, &column_family_id
, &key
, &value
,
217 case kTypeColumnFamilyValue
:
220 if (!UpdateExistingEntryWithCfId(column_family_id
, key
, kPutRecord
)) {
221 AddNewEntry(column_family_id
);
224 case kTypeColumnFamilyDeletion
:
227 if (!UpdateExistingEntryWithCfId(column_family_id
, key
,
229 AddNewEntry(column_family_id
);
232 case kTypeColumnFamilySingleDeletion
:
233 case kTypeSingleDeletion
:
235 if (!UpdateExistingEntryWithCfId(column_family_id
, key
,
236 kSingleDeleteRecord
)) {
237 AddNewEntry(column_family_id
);
240 case kTypeColumnFamilyMerge
:
243 if (!UpdateExistingEntryWithCfId(column_family_id
, key
, kMergeRecord
)) {
244 AddNewEntry(column_family_id
);
248 case kTypeBeginPrepareXID
:
249 case kTypeBeginPersistedPrepareXID
:
250 case kTypeBeginUnprepareXID
:
251 case kTypeEndPrepareXID
:
253 case kTypeCommitXIDAndTimestamp
:
254 case kTypeRollbackXID
:
258 return Status::Corruption(
259 "unknown WriteBatch tag in ReBuildIndex",
260 std::to_string(static_cast<unsigned int>(tag
)));
264 if (s
.ok() && found
!= write_batch
.Count()) {
265 s
= Status::Corruption("WriteBatch has wrong count");
271 WriteBatchWithIndex::WriteBatchWithIndex(
272 const Comparator
* default_index_comparator
, size_t reserved_bytes
,
273 bool overwrite_key
, size_t max_bytes
, size_t protection_bytes_per_key
)
274 : rep(new Rep(default_index_comparator
, reserved_bytes
, max_bytes
,
275 overwrite_key
, protection_bytes_per_key
)) {}
277 WriteBatchWithIndex::~WriteBatchWithIndex() {}
279 WriteBatchWithIndex::WriteBatchWithIndex(WriteBatchWithIndex
&&) = default;
281 WriteBatchWithIndex
& WriteBatchWithIndex::operator=(WriteBatchWithIndex
&&) =
284 WriteBatch
* WriteBatchWithIndex::GetWriteBatch() { return &rep
->write_batch
; }
286 size_t WriteBatchWithIndex::SubBatchCnt() { return rep
->sub_batch_cnt
; }
288 WBWIIterator
* WriteBatchWithIndex::NewIterator() {
289 return new WBWIIteratorImpl(0, &(rep
->skip_list
), &rep
->write_batch
,
293 WBWIIterator
* WriteBatchWithIndex::NewIterator(
294 ColumnFamilyHandle
* column_family
) {
295 return new WBWIIteratorImpl(GetColumnFamilyID(column_family
),
296 &(rep
->skip_list
), &rep
->write_batch
,
300 Iterator
* WriteBatchWithIndex::NewIteratorWithBase(
301 ColumnFamilyHandle
* column_family
, Iterator
* base_iterator
,
302 const ReadOptions
* read_options
) {
304 new WBWIIteratorImpl(GetColumnFamilyID(column_family
), &(rep
->skip_list
),
305 &rep
->write_batch
, &rep
->comparator
);
306 return new BaseDeltaIterator(column_family
, base_iterator
, wbwiii
,
307 GetColumnFamilyUserComparator(column_family
),
311 Iterator
* WriteBatchWithIndex::NewIteratorWithBase(Iterator
* base_iterator
) {
312 // default column family's comparator
313 auto wbwiii
= new WBWIIteratorImpl(0, &(rep
->skip_list
), &rep
->write_batch
,
315 return new BaseDeltaIterator(nullptr, base_iterator
, wbwiii
,
316 rep
->comparator
.default_comparator());
319 Status
WriteBatchWithIndex::Put(ColumnFamilyHandle
* column_family
,
320 const Slice
& key
, const Slice
& value
) {
321 rep
->SetLastEntryOffset();
322 auto s
= rep
->write_batch
.Put(column_family
, key
, value
);
324 rep
->AddOrUpdateIndex(column_family
, key
, kPutRecord
);
329 Status
WriteBatchWithIndex::Put(const Slice
& key
, const Slice
& value
) {
330 rep
->SetLastEntryOffset();
331 auto s
= rep
->write_batch
.Put(key
, value
);
333 rep
->AddOrUpdateIndex(key
, kPutRecord
);
338 Status
WriteBatchWithIndex::Put(ColumnFamilyHandle
* column_family
,
339 const Slice
& /*key*/, const Slice
& /*ts*/,
340 const Slice
& /*value*/) {
341 if (!column_family
) {
342 return Status::InvalidArgument("column family handle cannot be nullptr");
344 // TODO: support WBWI::Put() with timestamp.
345 return Status::NotSupported();
348 Status
WriteBatchWithIndex::Delete(ColumnFamilyHandle
* column_family
,
350 rep
->SetLastEntryOffset();
351 auto s
= rep
->write_batch
.Delete(column_family
, key
);
353 rep
->AddOrUpdateIndex(column_family
, key
, kDeleteRecord
);
358 Status
WriteBatchWithIndex::Delete(const Slice
& key
) {
359 rep
->SetLastEntryOffset();
360 auto s
= rep
->write_batch
.Delete(key
);
362 rep
->AddOrUpdateIndex(key
, kDeleteRecord
);
367 Status
WriteBatchWithIndex::Delete(ColumnFamilyHandle
* column_family
,
368 const Slice
& /*key*/, const Slice
& /*ts*/) {
369 if (!column_family
) {
370 return Status::InvalidArgument("column family handle cannot be nullptr");
372 // TODO: support WBWI::Delete() with timestamp.
373 return Status::NotSupported();
376 Status
WriteBatchWithIndex::SingleDelete(ColumnFamilyHandle
* column_family
,
378 rep
->SetLastEntryOffset();
379 auto s
= rep
->write_batch
.SingleDelete(column_family
, key
);
381 rep
->AddOrUpdateIndex(column_family
, key
, kSingleDeleteRecord
);
386 Status
WriteBatchWithIndex::SingleDelete(const Slice
& key
) {
387 rep
->SetLastEntryOffset();
388 auto s
= rep
->write_batch
.SingleDelete(key
);
390 rep
->AddOrUpdateIndex(key
, kSingleDeleteRecord
);
395 Status
WriteBatchWithIndex::SingleDelete(ColumnFamilyHandle
* column_family
,
396 const Slice
& /*key*/,
397 const Slice
& /*ts*/) {
398 if (!column_family
) {
399 return Status::InvalidArgument("column family handle cannot be nullptr");
401 // TODO: support WBWI::SingleDelete() with timestamp.
402 return Status::NotSupported();
405 Status
WriteBatchWithIndex::Merge(ColumnFamilyHandle
* column_family
,
406 const Slice
& key
, const Slice
& value
) {
407 rep
->SetLastEntryOffset();
408 auto s
= rep
->write_batch
.Merge(column_family
, key
, value
);
410 rep
->AddOrUpdateIndex(column_family
, key
, kMergeRecord
);
415 Status
WriteBatchWithIndex::Merge(const Slice
& key
, const Slice
& value
) {
416 rep
->SetLastEntryOffset();
417 auto s
= rep
->write_batch
.Merge(key
, value
);
419 rep
->AddOrUpdateIndex(key
, kMergeRecord
);
424 Status
WriteBatchWithIndex::PutLogData(const Slice
& blob
) {
425 return rep
->write_batch
.PutLogData(blob
);
428 void WriteBatchWithIndex::Clear() { rep
->Clear(); }
430 Status
WriteBatchWithIndex::GetFromBatch(ColumnFamilyHandle
* column_family
,
431 const DBOptions
& options
,
432 const Slice
& key
, std::string
* value
) {
434 WriteBatchWithIndexInternal
wbwii(&options
, column_family
);
435 auto result
= wbwii
.GetFromBatch(this, key
, value
, &s
);
438 case WBWIIteratorImpl::kFound
:
439 case WBWIIteratorImpl::kError
:
440 // use returned status
442 case WBWIIteratorImpl::kDeleted
:
443 case WBWIIteratorImpl::kNotFound
:
444 s
= Status::NotFound();
446 case WBWIIteratorImpl::kMergeInProgress
:
447 s
= Status::MergeInProgress();
456 Status
WriteBatchWithIndex::GetFromBatchAndDB(DB
* db
,
457 const ReadOptions
& read_options
,
459 std::string
* value
) {
460 assert(value
!= nullptr);
461 PinnableSlice
pinnable_val(value
);
462 assert(!pinnable_val
.IsPinned());
463 auto s
= GetFromBatchAndDB(db
, read_options
, db
->DefaultColumnFamily(), key
,
465 if (s
.ok() && pinnable_val
.IsPinned()) {
466 value
->assign(pinnable_val
.data(), pinnable_val
.size());
467 } // else value is already assigned
471 Status
WriteBatchWithIndex::GetFromBatchAndDB(DB
* db
,
472 const ReadOptions
& read_options
,
474 PinnableSlice
* pinnable_val
) {
475 return GetFromBatchAndDB(db
, read_options
, db
->DefaultColumnFamily(), key
,
479 Status
WriteBatchWithIndex::GetFromBatchAndDB(DB
* db
,
480 const ReadOptions
& read_options
,
481 ColumnFamilyHandle
* column_family
,
483 std::string
* value
) {
484 assert(value
!= nullptr);
485 PinnableSlice
pinnable_val(value
);
486 assert(!pinnable_val
.IsPinned());
488 GetFromBatchAndDB(db
, read_options
, column_family
, key
, &pinnable_val
);
489 if (s
.ok() && pinnable_val
.IsPinned()) {
490 value
->assign(pinnable_val
.data(), pinnable_val
.size());
491 } // else value is already assigned
495 Status
WriteBatchWithIndex::GetFromBatchAndDB(DB
* db
,
496 const ReadOptions
& read_options
,
497 ColumnFamilyHandle
* column_family
,
499 PinnableSlice
* pinnable_val
) {
500 return GetFromBatchAndDB(db
, read_options
, column_family
, key
, pinnable_val
,
504 Status
WriteBatchWithIndex::GetFromBatchAndDB(
505 DB
* db
, const ReadOptions
& read_options
, ColumnFamilyHandle
* column_family
,
506 const Slice
& key
, PinnableSlice
* pinnable_val
, ReadCallback
* callback
) {
507 const Comparator
* const ucmp
= rep
->comparator
.GetComparator(column_family
);
508 size_t ts_sz
= ucmp
? ucmp
->timestamp_size() : 0;
509 if (ts_sz
> 0 && !read_options
.timestamp
) {
510 return Status::InvalidArgument("Must specify timestamp");
514 WriteBatchWithIndexInternal
wbwii(db
, column_family
);
516 // Since the lifetime of the WriteBatch is the same as that of the transaction
517 // we cannot pin it as otherwise the returned value will not be available
518 // after the transaction finishes.
519 std::string
& batch_value
= *pinnable_val
->GetSelf();
520 auto result
= wbwii
.GetFromBatch(this, key
, &batch_value
, &s
);
522 if (result
== WBWIIteratorImpl::kFound
) {
523 pinnable_val
->PinSelf();
525 } else if (!s
.ok() || result
== WBWIIteratorImpl::kError
) {
527 } else if (result
== WBWIIteratorImpl::kDeleted
) {
528 return Status::NotFound();
530 assert(result
== WBWIIteratorImpl::kMergeInProgress
||
531 result
== WBWIIteratorImpl::kNotFound
);
533 // Did not find key in batch OR could not resolve Merges. Try DB.
535 s
= db
->Get(read_options
, column_family
, key
, pinnable_val
);
537 DBImpl::GetImplOptions get_impl_options
;
538 get_impl_options
.column_family
= column_family
;
539 get_impl_options
.value
= pinnable_val
;
540 get_impl_options
.callback
= callback
;
541 s
= static_cast_with_check
<DBImpl
>(db
->GetRootDB())
542 ->GetImpl(read_options
, key
, get_impl_options
);
545 if (s
.ok() || s
.IsNotFound()) { // DB Get Succeeded
546 if (result
== WBWIIteratorImpl::kMergeInProgress
) {
547 // Merge result from DB with merges in Batch
548 std::string merge_result
;
550 s
= wbwii
.MergeKey(key
, pinnable_val
, &merge_result
);
551 } else { // Key not present in db (s.IsNotFound())
552 s
= wbwii
.MergeKey(key
, nullptr, &merge_result
);
555 pinnable_val
->Reset();
556 *pinnable_val
->GetSelf() = std::move(merge_result
);
557 pinnable_val
->PinSelf();
565 void WriteBatchWithIndex::MultiGetFromBatchAndDB(
566 DB
* db
, const ReadOptions
& read_options
, ColumnFamilyHandle
* column_family
,
567 const size_t num_keys
, const Slice
* keys
, PinnableSlice
* values
,
568 Status
* statuses
, bool sorted_input
) {
569 MultiGetFromBatchAndDB(db
, read_options
, column_family
, num_keys
, keys
,
570 values
, statuses
, sorted_input
, nullptr);
573 void WriteBatchWithIndex::MultiGetFromBatchAndDB(
574 DB
* db
, const ReadOptions
& read_options
, ColumnFamilyHandle
* column_family
,
575 const size_t num_keys
, const Slice
* keys
, PinnableSlice
* values
,
576 Status
* statuses
, bool sorted_input
, ReadCallback
* callback
) {
577 const Comparator
* const ucmp
= rep
->comparator
.GetComparator(column_family
);
578 size_t ts_sz
= ucmp
? ucmp
->timestamp_size() : 0;
579 if (ts_sz
> 0 && !read_options
.timestamp
) {
580 for (size_t i
= 0; i
< num_keys
; ++i
) {
581 statuses
[i
] = Status::InvalidArgument("Must specify timestamp");
586 WriteBatchWithIndexInternal
wbwii(db
, column_family
);
588 autovector
<KeyContext
, MultiGetContext::MAX_BATCH_SIZE
> key_context
;
589 autovector
<KeyContext
*, MultiGetContext::MAX_BATCH_SIZE
> sorted_keys
;
590 // To hold merges from the write batch
591 autovector
<std::pair
<WBWIIteratorImpl::Result
, MergeContext
>,
592 MultiGetContext::MAX_BATCH_SIZE
>
594 // Since the lifetime of the WriteBatch is the same as that of the transaction
595 // we cannot pin it as otherwise the returned value will not be available
596 // after the transaction finishes.
597 for (size_t i
= 0; i
< num_keys
; ++i
) {
598 MergeContext merge_context
;
599 std::string batch_value
;
600 Status
* s
= &statuses
[i
];
601 PinnableSlice
* pinnable_val
= &values
[i
];
602 pinnable_val
->Reset();
604 wbwii
.GetFromBatch(this, keys
[i
], &merge_context
, &batch_value
, s
);
606 if (result
== WBWIIteratorImpl::kFound
) {
607 *pinnable_val
->GetSelf() = std::move(batch_value
);
608 pinnable_val
->PinSelf();
611 if (result
== WBWIIteratorImpl::kDeleted
) {
612 *s
= Status::NotFound();
615 if (result
== WBWIIteratorImpl::kError
) {
618 assert(result
== WBWIIteratorImpl::kMergeInProgress
||
619 result
== WBWIIteratorImpl::kNotFound
);
620 key_context
.emplace_back(column_family
, keys
[i
], &values
[i
],
621 /*timestamp*/ nullptr, &statuses
[i
]);
622 merges
.emplace_back(result
, std::move(merge_context
));
625 for (KeyContext
& key
: key_context
) {
626 sorted_keys
.emplace_back(&key
);
629 // Did not find key in batch OR could not resolve Merges. Try DB.
630 static_cast_with_check
<DBImpl
>(db
->GetRootDB())
631 ->PrepareMultiGetKeys(key_context
.size(), sorted_input
, &sorted_keys
);
632 static_cast_with_check
<DBImpl
>(db
->GetRootDB())
633 ->MultiGetWithCallback(read_options
, column_family
, callback
,
636 for (auto iter
= key_context
.begin(); iter
!= key_context
.end(); ++iter
) {
637 KeyContext
& key
= *iter
;
638 if (key
.s
->ok() || key
.s
->IsNotFound()) { // DB Get Succeeded
639 size_t index
= iter
- key_context
.begin();
640 std::pair
<WBWIIteratorImpl::Result
, MergeContext
>& merge_result
=
642 if (merge_result
.first
== WBWIIteratorImpl::kMergeInProgress
) {
643 std::string merged_value
;
644 // Merge result from DB with merges in Batch
646 *key
.s
= wbwii
.MergeKey(*key
.key
, iter
->value
, merge_result
.second
,
648 } else { // Key not present in db (s.IsNotFound())
649 *key
.s
= wbwii
.MergeKey(*key
.key
, nullptr, merge_result
.second
,
654 *key
.value
->GetSelf() = std::move(merged_value
);
655 key
.value
->PinSelf();
662 void WriteBatchWithIndex::SetSavePoint() { rep
->write_batch
.SetSavePoint(); }
664 Status
WriteBatchWithIndex::RollbackToSavePoint() {
665 Status s
= rep
->write_batch
.RollbackToSavePoint();
668 rep
->sub_batch_cnt
= 1;
669 rep
->last_sub_batch_offset
= 0;
670 s
= rep
->ReBuildIndex();
676 Status
WriteBatchWithIndex::PopSavePoint() {
677 return rep
->write_batch
.PopSavePoint();
680 void WriteBatchWithIndex::SetMaxBytes(size_t max_bytes
) {
681 rep
->write_batch
.SetMaxBytes(max_bytes
);
684 size_t WriteBatchWithIndex::GetDataSize() const {
685 return rep
->write_batch
.GetDataSize();
688 const Comparator
* WriteBatchWithIndexInternal::GetUserComparator(
689 const WriteBatchWithIndex
& wbwi
, uint32_t cf_id
) {
690 const WriteBatchEntryComparator
& ucmps
= wbwi
.rep
->comparator
;
691 return ucmps
.GetComparator(cf_id
);
694 } // namespace ROCKSDB_NAMESPACE
695 #endif // !ROCKSDB_LITE