@ -27,23 +27,23 @@ namespace ROCKSDB_NAMESPACE {
namespace {
// Creates a test blob file with a single blob in it. Note: this method
// makes it possible to test various corner cases by allowing the caller
// to specify the contents of various blob file header/footer fields.
// Creates a test blob file with `num` blobs in it.
void WriteBlobFile ( const ImmutableOptions & immutable_options ,
uint32_t column_family_id , bool has_ttl ,
const ExpirationRange & expiration_range_header ,
const ExpirationRange & expiration_range_footer ,
uint64_t blob_file_number , const Slice & key ,
const Slice & blob , CompressionType compression_type ,
uint64_t * blob_offset , uint64_t * blob_size ) {
uint64_t blob_file_number , const std : : vector < Slice > & keys ,
const std : : vector < Slice > & blobs , CompressionType compression ,
std : : vector < uint64_t > & blob_offsets ,
std : : vector < uint64_t > & blob_sizes ) {
assert ( ! immutable_options . cf_paths . empty ( ) ) ;
assert ( blob_offset ) ;
assert ( blob_size ) ;
size_t num = keys . size ( ) ;
assert ( num = = blobs . size ( ) ) ;
assert ( num = = blob_offsets . size ( ) ) ;
assert ( num = = blob_sizes . size ( ) ) ;
const std : : string blob_file_path =
BlobFileName ( immutable_options . cf_paths . front ( ) . path , blob_file_number ) ;
std : : unique_ptr < FSWritableFile > file ;
ASSERT_OK ( NewWritableFile ( immutable_options . fs . get ( ) , blob_file_path , & file ,
FileOptions ( ) ) ) ;
@ -59,50 +59,77 @@ void WriteBlobFile(const ImmutableOptions& immutable_options,
statistics , blob_file_number , use_fsync ,
do_flush ) ;
BlobLogHeader header ( column_family_id , compression_type , has_ttl ,
BlobLogHeader header ( column_family_id , compression , has_ttl ,
expiration_range_header ) ;
ASSERT_OK ( blob_log_writer . WriteHeader ( header ) ) ;
std : : string compressed_blob ;
Slice blob_to_write ;
if ( compression_type = = kNoCompression ) {
blob_to_write = blob ;
* blob_size = blob . size ( ) ;
std : : vector < std : : string > compressed_blobs ( num ) ;
std : : vector < Slice > blobs_to_write ( num ) ;
if ( kNoCompression = = compression ) {
for ( size_t i = 0 ; i < num ; + + i ) {
blobs_to_write [ i ] = blobs [ i ] ;
blob_sizes [ i ] = blobs [ i ] . size ( ) ;
}
} else {
CompressionOptions opts ;
CompressionContext context ( compression_type ) ;
CompressionContext context ( compression ) ;
constexpr uint64_t sample_for_compression = 0 ;
CompressionInfo info ( opts , context , CompressionDict : : GetEmptyDict ( ) ,
compression_type , sample_for_compression ) ;
compression , sample_for_compression ) ;
constexpr uint32_t compression_format_version = 2 ;
ASSERT_TRUE (
CompressData ( blob , info , compression_format_version , & compressed_blob ) ) ;
blob_to_write = compressed_blob ;
* blob_size = compressed_blob . size ( ) ;
for ( size_t i = 0 ; i < num ; + + i ) {
ASSERT_TRUE ( CompressData ( blobs [ i ] , info , compression_format_version ,
& compressed_blobs [ i ] ) ) ;
blobs_to_write [ i ] = compressed_blobs [ i ] ;
blob_sizes [ i ] = compressed_blobs [ i ] . size ( ) ;
}
}
for ( size_t i = 0 ; i < num ; + + i ) {
uint64_t key_offset = 0 ;
ASSERT_OK (
blob_log_writer . AddRecord ( key , blob_to_write , & key_offset , blob_offset ) ) ;
ASSERT_OK ( blob_log_writer . AddRecord ( keys [ i ] , blobs_to_write [ i ] , & key_offset ,
& blob_offsets [ i ] ) ) ;
}
BlobLogFooter footer ;
footer . blob_count = 1 ;
footer . blob_count = num ;
footer . expiration_range = expiration_range_footer ;
std : : string checksum_method ;
std : : string checksum_value ;
ASSERT_OK (
blob_log_writer . AppendFooter ( footer , & checksum_method , & checksum_value ) ) ;
}
// Creates a test blob file with a single blob in it. Note: this method
// makes it possible to test various corner cases by allowing the caller
// to specify the contents of various blob file header/footer fields.
void WriteBlobFile ( const ImmutableOptions & immutable_options ,
uint32_t column_family_id , bool has_ttl ,
const ExpirationRange & expiration_range_header ,
const ExpirationRange & expiration_range_footer ,
uint64_t blob_file_number , const Slice & key ,
const Slice & blob , CompressionType compression ,
uint64_t * blob_offset , uint64_t * blob_size ) {
std : : vector < Slice > keys { key } ;
std : : vector < Slice > blobs { blob } ;
std : : vector < uint64_t > blob_offsets { 0 } ;
std : : vector < uint64_t > blob_sizes { 0 } ;
WriteBlobFile ( immutable_options , column_family_id , has_ttl ,
expiration_range_header , expiration_range_footer ,
blob_file_number , keys , blobs , compression , blob_offsets ,
blob_sizes ) ;
if ( blob_offset ) {
* blob_offset = blob_offsets [ 0 ] ;
}
if ( blob_size ) {
* blob_size = blob_sizes [ 0 ] ;
}
}
} // anonymous namespace
class BlobFileReaderTest : public testing : : Test {
@ -127,15 +154,19 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
constexpr bool has_ttl = false ;
constexpr ExpirationRange expiration_range ;
constexpr uint64_t blob_file_number = 1 ;
constexpr char key [ ] = " key " ;
constexpr char blob [ ] = " blob " ;
constexpr size_t num_blobs = 3 ;
const std : : vector < std : : string > key_strs = { " key1 " , " key2 " , " key3 " } ;
const std : : vector < std : : string > blob_strs = { " blob1 " , " blob2 " , " blob3 " } ;
uint64_t blob_offset = 0 ;
uint64_t blob_size = 0 ;
const std : : vector < Slice > keys = { key_strs [ 0 ] , key_strs [ 1 ] , key_strs [ 2 ] } ;
const std : : vector < Slice > blobs = { blob_strs [ 0 ] , blob_strs [ 1 ] , blob_strs [ 2 ] } ;
std : : vector < uint64_t > blob_offsets ( keys . size ( ) ) ;
std : : vector < uint64_t > blob_sizes ( keys . size ( ) ) ;
WriteBlobFile ( immutable_options , column_family_id , has_ttl , expiration_range ,
expiration_range , blob_file_number , key , blob , kNoCompression ,
& blob_offset , & blob_size ) ;
expiration_range , blob_file_number , keys , blobs , kNoCompression ,
blob_offsets , blob_sizes ) ;
constexpr HistogramImpl * blob_file_read_hist = nullptr ;
@ -153,10 +184,36 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
PinnableSlice value ;
uint64_t bytes_read = 0 ;
ASSERT_OK ( reader - > GetBlob ( read_options , key , blob_offset , blob_size ,
kNoCompression , & value , & bytes_read ) ) ;
ASSERT_EQ ( value , blob ) ;
ASSERT_EQ ( bytes_read , blob_size ) ;
ASSERT_OK ( reader - > GetBlob ( read_options , keys [ 0 ] , blob_offsets [ 0 ] ,
blob_sizes [ 0 ] , kNoCompression , & value ,
& bytes_read ) ) ;
ASSERT_EQ ( value , blobs [ 0 ] ) ;
ASSERT_EQ ( bytes_read , blob_sizes [ 0 ] ) ;
// MultiGetBlob
bytes_read = 0 ;
size_t total_size = 0 ;
autovector < std : : reference_wrapper < const Slice > > key_refs ;
for ( const auto & key_ref : keys ) {
key_refs . emplace_back ( std : : cref ( key_ref ) ) ;
}
autovector < uint64_t > offsets { blob_offsets [ 0 ] , blob_offsets [ 1 ] ,
blob_offsets [ 2 ] } ;
autovector < uint64_t > sizes { blob_sizes [ 0 ] , blob_sizes [ 1 ] , blob_sizes [ 2 ] } ;
std : : array < Status , num_blobs > statuses_buf ;
autovector < Status * > statuses { & statuses_buf [ 0 ] , & statuses_buf [ 1 ] ,
& statuses_buf [ 2 ] } ;
std : : array < PinnableSlice , num_blobs > value_buf ;
autovector < PinnableSlice * > values { & value_buf [ 0 ] , & value_buf [ 1 ] ,
& value_buf [ 2 ] } ;
reader - > MultiGetBlob ( read_options , key_refs , offsets , sizes , statuses ,
values , & bytes_read ) ;
for ( size_t i = 0 ; i < num_blobs ; + + i ) {
ASSERT_OK ( statuses_buf [ i ] ) ;
ASSERT_EQ ( value_buf [ i ] , blobs [ i ] ) ;
total_size + = blob_sizes [ i ] ;
}
ASSERT_EQ ( bytes_read , total_size ) ;
}
read_options . verify_checksums = true ;
@ -165,14 +222,15 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
PinnableSlice value ;
uint64_t bytes_read = 0 ;
ASSERT_OK ( reader - > GetBlob ( read_options , key , blob_offset , blob_size ,
kNoCompression , & value , & bytes_read ) ) ;
ASSERT_EQ ( value , blob ) ;
ASSERT_OK ( reader - > GetBlob ( read_options , keys [ 1 ] , blob_offsets [ 1 ] ,
blob_sizes [ 1 ] , kNoCompression , & value ,
& bytes_read ) ) ;
ASSERT_EQ ( value , blobs [ 1 ] ) ;
constexpr uint64_t key_size = sizeof ( key ) - 1 ;
const uint64_t key_size = keys [ 1 ] . size ( ) ;
ASSERT_EQ ( bytes_read ,
BlobLogRecord : : CalculateAdjustmentForRecordHeader ( key_size ) +
blob_size ) ;
blob_sizes [ 1 ] ) ;
}
// Invalid offset (too close to start of file)
@ -181,8 +239,9 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
uint64_t bytes_read = 0 ;
ASSERT_TRUE ( reader
- > GetBlob ( read_options , key , blob_offset - 1 , blob_size ,
kNoCompression , & value , & bytes_read )
- > GetBlob ( read_options , keys [ 0 ] , blob_offsets [ 0 ] - 1 ,
blob_sizes [ 0 ] , kNoCompression , & value ,
& bytes_read )
. IsCorruption ( ) ) ;
ASSERT_EQ ( bytes_read , 0 ) ;
}
@ -193,8 +252,9 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
uint64_t bytes_read = 0 ;
ASSERT_TRUE ( reader
- > GetBlob ( read_options , key , blob_offset + 1 , blob_size ,
kNoCompression , & value , & bytes_read )
- > GetBlob ( read_options , keys [ 2 ] , blob_offsets [ 2 ] + 1 ,
blob_sizes [ 2 ] , kNoCompression , & value ,
& bytes_read )
. IsCorruption ( ) ) ;
ASSERT_EQ ( bytes_read , 0 ) ;
}
@ -205,8 +265,8 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
uint64_t bytes_read = 0 ;
ASSERT_TRUE ( reader
- > GetBlob ( read_options , key , blob_offset , blob_size , kZSTD ,
& value , & bytes_read )
- > GetBlob ( read_options , keys [ 0 ] , blob_offsets [ 0 ] ,
blob_sizes [ 0 ] , kZSTD , & value , & bytes_read )
. IsCorruption ( ) ) ;
ASSERT_EQ ( bytes_read , 0 ) ;
}
@ -219,23 +279,82 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
ASSERT_TRUE ( reader
- > GetBlob ( read_options , shorter_key ,
blob_offset - ( sizeof ( key ) - sizeof ( shorter_key ) ) ,
blob_size , kNoCompression , & value , & bytes_read )
blob_offsets [ 0 ] -
( keys [ 0 ] . size ( ) - sizeof ( shorter_key ) + 1 ) ,
blob_sizes [ 0 ] , kNoCompression , & value ,
& bytes_read )
. IsCorruption ( ) ) ;
ASSERT_EQ ( bytes_read , 0 ) ;
// MultiGetBlob
autovector < std : : reference_wrapper < const Slice > > key_refs ;
for ( const auto & key_ref : keys ) {
key_refs . emplace_back ( std : : cref ( key_ref ) ) ;
}
Slice shorter_key_slice ( shorter_key , sizeof ( shorter_key ) - 1 ) ;
key_refs [ 1 ] = std : : cref ( shorter_key_slice ) ;
autovector < uint64_t > offsets {
blob_offsets [ 0 ] ,
blob_offsets [ 1 ] - ( keys [ 1 ] . size ( ) - key_refs [ 1 ] . get ( ) . size ( ) ) ,
blob_offsets [ 2 ] } ;
autovector < uint64_t > sizes { blob_sizes [ 0 ] , blob_sizes [ 1 ] , blob_sizes [ 2 ] } ;
std : : array < Status , num_blobs > statuses_buf ;
autovector < Status * > statuses { & statuses_buf [ 0 ] , & statuses_buf [ 1 ] ,
& statuses_buf [ 2 ] } ;
std : : array < PinnableSlice , num_blobs > value_buf ;
autovector < PinnableSlice * > values { & value_buf [ 0 ] , & value_buf [ 1 ] ,
& value_buf [ 2 ] } ;
reader - > MultiGetBlob ( read_options , key_refs , offsets , sizes , statuses ,
values , & bytes_read ) ;
for ( size_t i = 0 ; i < num_blobs ; + + i ) {
if ( i = = 1 ) {
ASSERT_TRUE ( statuses_buf [ i ] . IsCorruption ( ) ) ;
} else {
ASSERT_OK ( statuses_buf [ i ] ) ;
}
}
}
// Incorrect key
{
constexpr char incorrect_key [ ] = " foo " ;
constexpr char incorrect_key [ ] = " foo1 " ;
PinnableSlice value ;
uint64_t bytes_read = 0 ;
ASSERT_TRUE ( reader
- > GetBlob ( read_options , incorrect_key , blob_offset ,
blob_size , kNoCompression , & value , & bytes_read )
- > GetBlob ( read_options , incorrect_key , blob_offsets [ 0 ] ,
blob_sizes [ 0 ] , kNoCompression , & value ,
& bytes_read )
. IsCorruption ( ) ) ;
ASSERT_EQ ( bytes_read , 0 ) ;
// MultiGetBlob
autovector < std : : reference_wrapper < const Slice > > key_refs ;
for ( const auto & key_ref : keys ) {
key_refs . emplace_back ( std : : cref ( key_ref ) ) ;
}
Slice wrong_key_slice ( incorrect_key , sizeof ( incorrect_key ) - 1 ) ;
key_refs [ 2 ] = std : : cref ( wrong_key_slice ) ;
autovector < uint64_t > offsets { blob_offsets [ 0 ] , blob_offsets [ 1 ] ,
blob_offsets [ 2 ] } ;
autovector < uint64_t > sizes { blob_sizes [ 0 ] , blob_sizes [ 1 ] , blob_sizes [ 2 ] } ;
std : : array < Status , num_blobs > statuses_buf ;
autovector < Status * > statuses { & statuses_buf [ 0 ] , & statuses_buf [ 1 ] ,
& statuses_buf [ 2 ] } ;
std : : array < PinnableSlice , num_blobs > value_buf ;
autovector < PinnableSlice * > values { & value_buf [ 0 ] , & value_buf [ 1 ] ,
& value_buf [ 2 ] } ;
reader - > MultiGetBlob ( read_options , key_refs , offsets , sizes , statuses ,
values , & bytes_read ) ;
for ( size_t i = 0 ; i < num_blobs ; + + i ) {
if ( i = = num_blobs - 1 ) {
ASSERT_TRUE ( statuses_buf [ i ] . IsCorruption ( ) ) ;
} else {
ASSERT_OK ( statuses_buf [ i ] ) ;
}
}
}
// Incorrect value size
@ -244,10 +363,35 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
uint64_t bytes_read = 0 ;
ASSERT_TRUE ( reader
- > GetBlob ( read_options , key , blob_offset , blob_size + 1 ,
kNoCompression , & value , & bytes_read )
- > GetBlob ( read_options , keys [ 1 ] , blob_offsets [ 1 ] ,
blob_sizes [ 1 ] + 1 , kNoCompression , & value ,
& bytes_read )
. IsCorruption ( ) ) ;
ASSERT_EQ ( bytes_read , 0 ) ;
// MultiGetBlob
autovector < std : : reference_wrapper < const Slice > > key_refs ;
for ( const auto & key_ref : keys ) {
key_refs . emplace_back ( std : : cref ( key_ref ) ) ;
}
autovector < uint64_t > offsets { blob_offsets [ 0 ] , blob_offsets [ 1 ] ,
blob_offsets [ 2 ] } ;
autovector < uint64_t > sizes { blob_sizes [ 0 ] , blob_sizes [ 1 ] + 1 , blob_sizes [ 2 ] } ;
std : : array < Status , num_blobs > statuses_buf ;
autovector < Status * > statuses { & statuses_buf [ 0 ] , & statuses_buf [ 1 ] ,
& statuses_buf [ 2 ] } ;
std : : array < PinnableSlice , num_blobs > value_buf ;
autovector < PinnableSlice * > values { & value_buf [ 0 ] , & value_buf [ 1 ] ,
& value_buf [ 2 ] } ;
reader - > MultiGetBlob ( read_options , key_refs , offsets , sizes , statuses ,
values , & bytes_read ) ;
for ( size_t i = 0 ; i < num_blobs ; + + i ) {
if ( i ! = 1 ) {
ASSERT_OK ( statuses_buf [ i ] ) ;
} else {
ASSERT_TRUE ( statuses_buf [ i ] . IsCorruption ( ) ) ;
}
}
}
}