validate SstFileWriter range tombstones cover positive ranges (#11322)

Summary:
As titled. This is the same as https://github.com/facebook/rocksdb/issues/6788 but for range tombstones written through `SstFileWriter` rather than through `DB`.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11322

Reviewed By: cbi42

Differential Revision: D44317733

Pulled By: ajkr

fbshipit-source-id: f6eb8791ae2c09c169b6bfe0d047449d924b377e
oxigraph-8.3.2
Andrew Kryczka 2 years ago committed by Facebook GitHub Bot
parent 57abdea389
commit 9f8cdc8ad6
  1. 2
      HISTORY.md
  2. 26
      db/external_sst_file_basic_test.cc
  3. 4
      include/rocksdb/sst_file_writer.h
  4. 11
      table/sst_file_writer.cc

@ -1,5 +1,7 @@
# Rocksdb Change Log # Rocksdb Change Log
## Unreleased ## Unreleased
### Public API Changes
* `SstFileWriter::DeleteRange()` now returns `Status::InvalidArgument` if the range's end key comes before its start key according to the user comparator. Previously the behavior was undefined.
## 8.1.0 (03/18/2023) ## 8.1.0 (03/18/2023)
### Behavior changes ### Behavior changes

@ -1463,6 +1463,32 @@ TEST_F(ExternalSSTFileBasicTest, AdjacentRangeDeletionTombstones) {
DestroyAndRecreateExternalSSTFilesDir(); DestroyAndRecreateExternalSSTFilesDir();
} }
TEST_F(ExternalSSTFileBasicTest, RangeDeletionEndComesBeforeStart) {
Options options = CurrentOptions();
SstFileWriter sst_file_writer(EnvOptions(), options);
// "file.sst"
// Verify attempt to delete 300 => 200 fails.
// Then, verify attempt to delete 300 => 300 succeeds but writes nothing.
// Afterwards, verify attempt to delete 300 => 400 works normally.
std::string file = sst_files_dir_ + "file.sst";
ASSERT_OK(sst_file_writer.Open(file));
ASSERT_TRUE(
sst_file_writer.DeleteRange(Key(300), Key(200)).IsInvalidArgument());
ASSERT_OK(sst_file_writer.DeleteRange(Key(300), Key(300)));
ASSERT_OK(sst_file_writer.DeleteRange(Key(300), Key(400)));
ExternalSstFileInfo file_info;
Status s = sst_file_writer.Finish(&file_info);
ASSERT_OK(s) << s.ToString();
ASSERT_EQ(file_info.file_path, file);
ASSERT_EQ(file_info.num_entries, 0);
ASSERT_EQ(file_info.smallest_key, "");
ASSERT_EQ(file_info.largest_key, "");
ASSERT_EQ(file_info.num_range_del_entries, 1);
ASSERT_EQ(file_info.smallest_range_del_key, Key(300));
ASSERT_EQ(file_info.largest_range_del_key, Key(400));
}
TEST_P(ExternalSSTFileBasicTest, IngestFileWithBadBlockChecksum) { TEST_P(ExternalSSTFileBasicTest, IngestFileWithBadBlockChecksum) {
bool change_checksum_called = false; bool change_checksum_called = false;
const auto& change_checksum = [&](void* arg) { const auto& change_checksum = [&](void* arg) {

@ -145,12 +145,16 @@ class SstFileWriter {
// Add a range deletion tombstone to currently opened file. Such a range // Add a range deletion tombstone to currently opened file. Such a range
// deletion tombstone does NOT delete other (point) keys in the same file. // deletion tombstone does NOT delete other (point) keys in the same file.
//
// REQUIRES: The comparator orders `begin_key` at or before `end_key`
// REQUIRES: comparator is *not* timestamp-aware. // REQUIRES: comparator is *not* timestamp-aware.
Status DeleteRange(const Slice& begin_key, const Slice& end_key); Status DeleteRange(const Slice& begin_key, const Slice& end_key);
// Add a range deletion tombstone to currently opened file. Such a range // Add a range deletion tombstone to currently opened file. Such a range
// deletion tombstone does NOT delete other (point) keys in the same file. // deletion tombstone does NOT delete other (point) keys in the same file.
//
// REQUIRES: begin_key and end_key are user keys without timestamp. // REQUIRES: begin_key and end_key are user keys without timestamp.
// REQUIRES: The comparator orders `begin_key` at or before `end_key`
// REQUIRES: the timestamp's size is equal to what is expected by // REQUIRES: the timestamp's size is equal to what is expected by
// the comparator. // the comparator.
Status DeleteRange(const Slice& begin_key, const Slice& end_key, Status DeleteRange(const Slice& begin_key, const Slice& end_key,

@ -134,6 +134,17 @@ struct SstFileWriter::Rep {
if (!builder) { if (!builder) {
return Status::InvalidArgument("File is not opened"); return Status::InvalidArgument("File is not opened");
} }
int cmp = internal_comparator.user_comparator()->CompareWithoutTimestamp(
begin_key, end_key);
if (cmp > 0) {
// It's an empty range where endpoints appear mistaken. Don't bother
// applying it to the DB, and return an error to the user.
return Status::InvalidArgument("end key comes before start key");
} else if (cmp == 0) {
// It's an empty range. Don't bother applying it to the DB.
return Status::OK();
}
RangeTombstone tombstone(begin_key, end_key, 0 /* Sequence Number */); RangeTombstone tombstone(begin_key, end_key, 0 /* Sequence Number */);
if (file_info.num_range_del_entries == 0) { if (file_info.num_range_del_entries == 0) {
file_info.smallest_range_del_key.assign(tombstone.start_key_.data(), file_info.smallest_range_del_key.assign(tombstone.start_key_.data(),

Loading…
Cancel
Save