Coverage Report

Created: 2026-11-12 08:06

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/rocksdb/util/compression.cc
Line
Count
Source
1
// Copyright (c) 2022-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).
5
6
#include "util/compression.h"
7
8
#ifdef BZIP2
9
#include <bzlib.h>
10
#endif  // BZIP2
11
12
#include <limits>
13
14
#ifdef LZ4
15
#include <lz4.h>
16
#include <lz4hc.h>
17
#if LZ4_VERSION_NUMBER < 10700  // < r129
18
#error "LZ4 support requires version >= 1.7.0 (lz4-devel)"
19
#endif  // LZ4_VERSION_NUMBER < 10700
20
#endif  // LZ4
21
22
#ifdef SNAPPY
23
#include <snappy-sinksource.h>
24
#include <snappy.h>
25
#endif  // SNAPPY
26
27
#ifdef ZLIB
28
#include <zlib.h>
29
#endif  // ZLIB
30
31
#include "options/options_helper.h"
32
#include "port/likely.h"
33
#include "rocksdb/convenience.h"
34
#include "rocksdb/utilities/object_registry.h"
35
#include "test_util/sync_point.h"
36
#include "util/cast_util.h"
37
#include "util/string_util.h"
38
39
namespace ROCKSDB_NAMESPACE {
40
41
// WART: does not match OptionsHelper::compression_type_string_map
42
182k
std::string CompressionTypeToString(CompressionType compression_type) {
43
182k
  switch (compression_type) {
44
182k
    case kNoCompression:
45
182k
      return "NoCompression";
46
0
    case kSnappyCompression:
47
0
      return "Snappy";
48
0
    case kZlibCompression:
49
0
      return "Zlib";
50
0
    case kBZip2Compression:
51
0
      return "BZip2";
52
0
    case kLZ4Compression:
53
0
      return "LZ4";
54
0
    case kLZ4HCCompression:
55
0
      return "LZ4HC";
56
0
    case kXpressCompression:
57
0
      return "Xpress";
58
0
    case kZSTD:
59
0
      return "ZSTD";
60
0
    case kDisableCompressionOption:
61
0
      return "DisableOption";
62
0
    default: {
63
0
      bool is_custom = compression_type >= kFirstCustomCompression &&
64
0
                       compression_type <= kLastCustomCompression;
65
0
      unsigned char c = lossless_cast<unsigned char>(compression_type);
66
0
      return (is_custom ? "Custom" : "Reserved") +
67
0
             ToBaseCharsString<16>(2, c, /*uppercase=*/true);
68
0
    }
69
182k
  }
70
182k
}
71
72
// WART: does not match OptionsHelper::compression_type_string_map
73
0
CompressionType CompressionTypeFromString(std::string compression_type_str) {
74
0
  if (!compression_type_str.empty()) {
75
0
    switch (compression_type_str[0]) {
76
0
      case 'N':
77
0
        if (compression_type_str == "NoCompression") {
78
0
          return kNoCompression;
79
0
        }
80
0
        break;
81
0
      case 'S':
82
0
        if (compression_type_str == "Snappy") {
83
0
          return kSnappyCompression;
84
0
        }
85
0
        break;
86
0
      case 'Z':
87
0
        if (compression_type_str == "ZSTD") {
88
0
          return kZSTD;
89
0
        }
90
0
        if (compression_type_str == "Zlib") {
91
0
          return kZlibCompression;
92
0
        }
93
0
        break;
94
0
      case 'B':
95
0
        if (compression_type_str == "BZip2") {
96
0
          return kBZip2Compression;
97
0
        }
98
0
        break;
99
0
      case 'L':
100
0
        if (compression_type_str == "LZ4") {
101
0
          return kLZ4Compression;
102
0
        }
103
0
        if (compression_type_str == "LZ4HC") {
104
0
          return kLZ4HCCompression;
105
0
        }
106
0
        break;
107
0
      case 'X':
108
0
        if (compression_type_str == "Xpress") {
109
0
          return kXpressCompression;
110
0
        }
111
0
        break;
112
0
      default:;
113
0
    }
114
0
  }
115
  // unrecognized
116
0
  return kDisableCompressionOption;
117
0
}
118
119
std::string CompressionOptionsToString(
120
19.6k
    const CompressionOptions& compression_options) {
121
19.6k
  std::string result;
122
19.6k
  result.reserve(512);
123
19.6k
  result.append("window_bits=")
124
19.6k
      .append(std::to_string(compression_options.window_bits))
125
19.6k
      .append("; ");
126
19.6k
  result.append("level=")
127
19.6k
      .append(std::to_string(compression_options.level))
128
19.6k
      .append("; ");
129
19.6k
  result.append("strategy=")
130
19.6k
      .append(std::to_string(compression_options.strategy))
131
19.6k
      .append("; ");
132
19.6k
  result.append("max_dict_bytes=")
133
19.6k
      .append(std::to_string(compression_options.max_dict_bytes))
134
19.6k
      .append("; ");
135
19.6k
  result.append("zstd_max_train_bytes=")
136
19.6k
      .append(std::to_string(compression_options.zstd_max_train_bytes))
137
19.6k
      .append("; ");
138
  // NOTE: parallel_threads is skipped because it doesn't really affect the file
139
  // contents written, arguably doesn't belong in CompressionOptions
140
19.6k
  result.append("enabled=")
141
19.6k
      .append(std::to_string(compression_options.enabled))
142
19.6k
      .append("; ");
143
19.6k
  result.append("max_dict_buffer_bytes=")
144
19.6k
      .append(std::to_string(compression_options.max_dict_buffer_bytes))
145
19.6k
      .append("; ");
146
19.6k
  result.append("use_zstd_dict_trainer=")
147
19.6k
      .append(std::to_string(compression_options.use_zstd_dict_trainer))
148
19.6k
      .append("; ");
149
19.6k
  result.append("max_compressed_bytes_per_kb=")
150
19.6k
      .append(std::to_string(compression_options.max_compressed_bytes_per_kb))
151
19.6k
      .append("; ");
152
  // Only emitted when enabled: auto_skip is experimental and default-off, so
153
  // keeping it out of the common case avoids enlarging every SST's persisted
154
  // compression_options property. Emitting it unconditionally grows every SST
155
  // by ~40 bytes, which overflows the tight size tolerance in
156
  // BlockBasedTableTest.CompressionRatioThreshold (table/table_test.cc). (The
157
  // canonical options serialization always includes it; this is a curated
158
  // summary.) auto_skip_min_sample_every is only meaningful when auto_skip is
159
  // on, so it is gated the same way.
160
19.6k
  if (compression_options.auto_skip) {
161
0
    result.append("auto_skip=")
162
0
        .append(std::to_string(compression_options.auto_skip))
163
0
        .append("; ");
164
0
    result.append("auto_skip_min_sample_every=")
165
0
        .append(std::to_string(compression_options.auto_skip_min_sample_every))
166
0
        .append("; ");
167
0
  }
168
19.6k
  result.append("checksum=")
169
19.6k
      .append(std::to_string(compression_options.checksum))
170
19.6k
      .append("; ");
171
19.6k
  return result;
172
19.6k
}
173
174
std::unique_ptr<StreamingCompress> StreamingCompress::Create(
175
    CompressionType compression_type, const CompressionOptions& opts,
176
0
    uint32_t compress_format_version, size_t max_output_len) {
177
0
  switch (compression_type) {
178
0
    case kZSTD: {
179
0
      if (!ZSTD_Streaming_Supported()) {
180
0
        return nullptr;
181
0
      }
182
0
      return std::make_unique<ZSTDStreamingCompress>(
183
0
          opts, compress_format_version, max_output_len);
184
0
    }
185
0
    default:
186
0
      return nullptr;
187
0
  }
188
0
}
189
190
std::unique_ptr<StreamingUncompress> StreamingUncompress::Create(
191
    CompressionType compression_type, uint32_t compress_format_version,
192
0
    size_t max_output_len) {
193
0
  switch (compression_type) {
194
0
    case kZSTD: {
195
0
      if (!ZSTD_Streaming_Supported()) {
196
0
        return nullptr;
197
0
      }
198
0
      return std::make_unique<ZSTDStreamingUncompress>(compress_format_version,
199
0
                                                       max_output_len);
200
0
    }
201
0
    default:
202
0
      return nullptr;
203
0
  }
204
0
}
205
206
int ZSTDStreamingCompress::Compress(const char* input, size_t input_size,
207
0
                                    char* output, size_t* output_pos) {
208
0
  assert(input != nullptr && output != nullptr && output_pos != nullptr);
209
0
  *output_pos = 0;
210
  // Don't need to compress an empty input
211
0
  if (input_size == 0) {
212
0
    return 0;
213
0
  }
214
0
#ifndef ZSTD
215
0
  (void)input;
216
0
  (void)input_size;
217
0
  (void)output;
218
0
  return -1;
219
#else
220
  if (input_buffer_.src == nullptr || input_buffer_.src != input) {
221
    // New input
222
    // Catch errors where the previous input was not fully decompressed.
223
    assert(input_buffer_.pos == input_buffer_.size);
224
    input_buffer_ = {input, input_size, /*pos=*/0};
225
  } else if (input_buffer_.src == input) {
226
    // Same input, not fully compressed.
227
  }
228
  ZSTD_outBuffer output_buffer = {output, max_output_len_, /*pos=*/0};
229
  const size_t remaining =
230
      ZSTD_compressStream2(cctx_, &output_buffer, &input_buffer_, ZSTD_e_end);
231
  if (ZSTD_isError(remaining)) {
232
    // Failure
233
    Reset();
234
    return -1;
235
  }
236
  // Success
237
  *output_pos = output_buffer.pos;
238
  return (int)remaining;
239
#endif
240
0
}
241
242
0
void ZSTDStreamingCompress::Reset() {
243
#ifdef ZSTD
244
  ZSTD_CCtx_reset(cctx_, ZSTD_ResetDirective::ZSTD_reset_session_only);
245
  input_buffer_ = {/*src=*/nullptr, /*size=*/0, /*pos=*/0};
246
#endif
247
0
}
248
249
int ZSTDStreamingUncompress::Uncompress(const char* input, size_t input_size,
250
0
                                        char* output, size_t* output_pos) {
251
0
  assert(output != nullptr && output_pos != nullptr);
252
0
  *output_pos = 0;
253
  // Don't need to uncompress an empty input
254
0
  if (input_size == 0) {
255
0
    return 0;
256
0
  }
257
#ifdef ZSTD
258
  if (input) {
259
    // New input
260
    input_buffer_ = {input, input_size, /*pos=*/0};
261
  }
262
  ZSTD_outBuffer output_buffer = {output, max_output_len_, /*pos=*/0};
263
  size_t ret = ZSTD_decompressStream(dctx_, &output_buffer, &input_buffer_);
264
  if (ZSTD_isError(ret)) {
265
    Reset();
266
    return -1;
267
  }
268
  *output_pos = output_buffer.pos;
269
  return (int)(input_buffer_.size - input_buffer_.pos);
270
#else
271
0
  (void)input;
272
0
  (void)input_size;
273
0
  (void)output;
274
0
  return -1;
275
0
#endif
276
0
}
277
278
0
void ZSTDStreamingUncompress::Reset() {
279
#ifdef ZSTD
280
  ZSTD_DCtx_reset(dctx_, ZSTD_ResetDirective::ZSTD_reset_session_only);
281
  input_buffer_ = {/*src=*/nullptr, /*size=*/0, /*pos=*/0};
282
#endif
283
0
}
284
285
0
void DecompressorDict::Populate(Decompressor& from_decompressor, Slice dict) {
286
0
  if (UNLIKELY(dict.empty())) {
287
0
    dict_str_ = {};
288
0
    dict_allocation_ = {};
289
    // Appropriately reject bad files with empty dictionary block.
290
    // It is longstanding not to write an empty dictionary block:
291
    // https://github.com/meta/rocksdb/blame/10.2.fb/table/block_based/block_based_table_builder.cc#L1841
292
0
    decompressor_ = std::make_unique<FailureDecompressor>(
293
0
        Status::Corruption("Decompression dictionary is empty"));
294
0
  } else {
295
0
    Status s = from_decompressor.MaybeCloneForDict(dict, &decompressor_);
296
0
    if (decompressor_ == nullptr) {
297
0
      dict_str_ = {};
298
0
      dict_allocation_ = {};
299
0
      assert(!s.ok());
300
0
      decompressor_ = std::make_unique<FailureDecompressor>(std::move(s));
301
0
    } else {
302
0
      assert(s.ok());
303
0
      assert(decompressor_->GetSerializedDict() == dict);
304
0
    }
305
0
  }
306
307
0
  memory_usage_ = sizeof(struct DecompressorDict);
308
0
  memory_usage_ += dict_str_.size();
309
0
  if (dict_allocation_) {
310
0
    auto allocator = dict_allocation_.get_deleter().allocator;
311
0
    if (allocator) {
312
0
      memory_usage_ +=
313
0
          allocator->UsableSize(dict_allocation_.get(), GetRawDict().size());
314
0
    } else {
315
0
      memory_usage_ += GetRawDict().size();
316
0
    }
317
0
  }
318
0
  memory_usage_ += decompressor_->ApproximateOwnedMemoryUsage();
319
0
}
320
321
// ZSTD dictionary training implementations
322
std::string ZSTD_TrainDictionary(const std::string& samples,
323
                                 const std::vector<size_t>& sample_lens,
324
0
                                 size_t max_dict_bytes) {
325
#ifdef ZSTD
326
  assert(samples.empty() == sample_lens.empty());
327
  if (samples.empty()) {
328
    return "";
329
  }
330
  std::string dict_data(max_dict_bytes, '\0');
331
  size_t dict_len = ZDICT_trainFromBuffer(
332
      &dict_data[0], max_dict_bytes, &samples[0], &sample_lens[0],
333
      static_cast<unsigned>(sample_lens.size()));
334
  if (ZDICT_isError(dict_len)) {
335
    return "";
336
  }
337
  assert(dict_len <= max_dict_bytes);
338
  dict_data.resize(dict_len);
339
  return dict_data;
340
#else
341
0
  assert(false);
342
0
  (void)samples;
343
0
  (void)sample_lens;
344
0
  (void)max_dict_bytes;
345
0
  return "";
346
0
#endif  // ZSTD
347
0
}
348
349
std::string ZSTD_TrainDictionary(const std::string& samples,
350
                                 size_t sample_len_shift,
351
0
                                 size_t max_dict_bytes) {
352
#ifdef ZSTD
353
  // skips potential partial sample at the end of "samples"
354
  size_t num_samples = samples.size() >> sample_len_shift;
355
  std::vector<size_t> sample_lens(num_samples, size_t(1) << sample_len_shift);
356
  return ZSTD_TrainDictionary(samples, sample_lens, max_dict_bytes);
357
#else
358
0
  assert(false);
359
0
  (void)samples;
360
0
  (void)sample_len_shift;
361
0
  (void)max_dict_bytes;
362
0
  return "";
363
0
#endif  // ZSTD
364
0
}
365
366
std::string ZSTD_FinalizeDictionary(const std::string& samples,
367
                                    const std::vector<size_t>& sample_lens,
368
0
                                    size_t max_dict_bytes, int level) {
369
#ifdef ROCKSDB_ZDICT_FINALIZE
370
  assert(samples.empty() == sample_lens.empty());
371
  if (samples.empty()) {
372
    return "";
373
  }
374
  level = SanitizeZSTDCompressionLevel(level);
375
  std::string dict_data(max_dict_bytes, '\0');
376
  size_t dict_len = ZDICT_finalizeDictionary(
377
      dict_data.data(), max_dict_bytes, samples.data(),
378
      std::min(static_cast<size_t>(samples.size()), max_dict_bytes),
379
      samples.data(), sample_lens.data(),
380
      static_cast<unsigned>(sample_lens.size()),
381
      {level, 0 /* notificationLevel */, 0 /* dictID */});
382
  if (ZDICT_isError(dict_len)) {
383
    return "";
384
  } else {
385
    assert(dict_len <= max_dict_bytes);
386
    dict_data.resize(dict_len);
387
    return dict_data;
388
  }
389
#else
390
0
  assert(false);
391
0
  (void)samples;
392
0
  (void)sample_lens;
393
0
  (void)max_dict_bytes;
394
0
  (void)level;
395
0
  return "";
396
0
#endif  // ROCKSDB_ZDICT_FINALIZE
397
0
}
398
399
// ***********************************************************************
400
// BEGIN built-in implementation of customization interface
401
// ***********************************************************************
402
0
Status Decompressor::ExtractUncompressedSize(Args& args) {
403
  // Default implementation:
404
  //
405
  // Standard format for prepending uncompressed size to the compressed
406
  // payload. (RocksDB compress_format_version=2 except Snappy)
407
  //
408
  // This is historically a varint32, but it is preliminarily generalized
409
  // to varint64, in case that is supported on the write side for some
410
  // algorithms.
411
0
  if (LIKELY(GetVarint64(&args.compressed_data, &args.uncompressed_size))) {
412
0
    if (LIKELY(args.uncompressed_size <= SIZE_MAX)) {
413
0
      return Status::OK();
414
0
    } else {
415
0
      return Status::MemoryLimit("Uncompressed size too large for platform");
416
0
    }
417
0
  } else {
418
0
    return Status::Corruption("Unable to extract uncompressed size");
419
0
  }
420
0
}
421
422
0
const Slice& Decompressor::GetSerializedDict() const {
423
  // Default: empty slice => no dictionary
424
0
  static Slice kEmptySlice;
425
0
  return kEmptySlice;
426
0
}
427
428
namespace {
429
430
class CompressorBase : public Compressor {
431
 public:
432
0
  explicit CompressorBase(const CompressionOptions& opts) : opts_(opts) {}
433
434
0
  uint32_t GetRecommendedParallelThreads() const override {
435
0
    return opts_.parallel_threads;
436
0
  }
437
438
 protected:
439
  CompressionOptions opts_;
440
};
441
442
class CompressorWithSimpleDictBase : public CompressorBase {
443
 public:
444
  explicit CompressorWithSimpleDictBase(const CompressionOptions& opts,
445
                                        std::string&& dict_data = {})
446
0
      : CompressorBase(opts), dict_data_(std::move(dict_data)) {}
447
448
0
  DictConfig GetDictGuidance(CacheEntryRole /*block_type*/) const override {
449
0
    if (opts_.max_dict_bytes == 0) {
450
0
      return DictDisabled{};
451
0
    }
452
0
    return DictSampling{opts_.max_dict_bytes};
453
0
  }
454
455
  // NOTE: empty dict is equivalent to no dict
456
0
  Slice GetSerializedDict() const override { return dict_data_; }
457
458
0
  std::unique_ptr<Compressor> Clone() const override {
459
0
    return CloneForDict(std::string{dict_data_});
460
0
  }
461
462
  std::unique_ptr<Compressor> MaybeCloneSpecialized(
463
0
      CacheEntryRole /*block_type*/, DictConfigArgs&& dict_config) const final {
464
0
    if (auto* samples = std::get_if<DictSamples>(&dict_config)) {
465
0
      assert(samples->Verify());
466
0
      if (samples->empty()) {
467
0
        return nullptr;
468
0
      }
469
0
      return CloneForDict(std::move(samples->sample_data));
470
0
    } else if (auto* predef = std::get_if<DictPreDefined>(&dict_config)) {
471
0
      if (predef->dict_data.empty()) {
472
0
        return nullptr;
473
0
      }
474
0
      return CloneForDict(std::move(predef->dict_data));
475
0
    } else {
476
0
      assert(std::holds_alternative<DictDisabled>(dict_config));
477
0
      return nullptr;
478
0
    }
479
0
  }
480
481
  virtual std::unique_ptr<Compressor> CloneForDict(
482
      std::string&& dict_data) const = 0;
483
484
 protected:
485
  const std::string dict_data_;
486
};
487
488
// NOTE: the legacy behavior is to pretend to use dictionary compression when
489
// enabled, including storing a dictionary block, but to ignore it. That is
490
// matched here.
491
class BuiltinSnappyCompressorV2 final : public CompressorWithSimpleDictBase {
492
 public:
493
  using CompressorWithSimpleDictBase::CompressorWithSimpleDictBase;
494
495
0
  const char* Name() const override { return "BuiltinSnappyCompressorV2"; }
496
497
0
  CompressionType GetPreferredCompressionType() const override {
498
0
    return kSnappyCompression;
499
0
  }
500
501
  // Snappy is more often SLOWER with parallel compression than faster.
502
0
  uint32_t GetRecommendedParallelThreads() const override { return 1; }
503
504
  std::unique_ptr<Compressor> CloneForDict(
505
0
      std::string&& dict_data) const override {
506
0
    return std::make_unique<BuiltinSnappyCompressorV2>(opts_,
507
0
                                                       std::move(dict_data));
508
0
  }
509
510
  Status CompressBlock(Slice uncompressed_data, char* compressed_output,
511
                       size_t* compressed_output_size,
512
                       CompressionType* out_compression_type,
513
0
                       ManagedWorkingArea*) override {
514
#ifdef SNAPPY
515
    struct MySink : public snappy::Sink {
516
      MySink(char* output, size_t output_size)
517
          : output_(output), output_size_(output_size) {}
518
519
      char* output_;
520
      size_t output_size_;
521
      size_t pos_ = 0;
522
523
      void Append(const char* data, size_t n) override {
524
        if (pos_ + n <= output_size_) {
525
          std::memcpy(output_ + pos_, data, n);
526
          pos_ += n;
527
        } else {
528
          // Virtual abort
529
          pos_ = output_size_ + 1;
530
        }
531
      }
532
533
      char* GetAppendBuffer(size_t length, char* scratch) override {
534
        if (pos_ + length <= output_size_) {
535
          return output_ + pos_;
536
        }
537
        return scratch;
538
      }
539
    };
540
    MySink sink{compressed_output, *compressed_output_size};
541
    snappy::ByteArraySource source{uncompressed_data.data(),
542
                                   uncompressed_data.size()};
543
544
    size_t outlen = snappy::Compress(&source, &sink);
545
    if (outlen > 0 && sink.pos_ <= sink.output_size_) {
546
      // Compression kept/successful
547
      assert(outlen == sink.pos_);
548
      *compressed_output_size = outlen;
549
      *out_compression_type = kSnappyCompression;
550
      return Status::OK();
551
    }
552
    // Compression rejected
553
    *compressed_output_size = 1;
554
#else
555
0
    (void)uncompressed_data;
556
0
    (void)compressed_output;
557
    // Compression bypassed (not supported)
558
0
    *compressed_output_size = 0;
559
0
#endif
560
0
    *out_compression_type = kNoCompression;
561
0
    return Status::OK();
562
0
  }
563
564
  std::shared_ptr<Decompressor> GetOptimizedDecompressor() const override;
565
};
566
567
[[maybe_unused]]
568
std::pair<char*, size_t> StartCompressBlockV2(Slice uncompressed_data,
569
                                              char* compressed_output,
570
0
                                              size_t compressed_output_size) {
571
0
  if (  // Can't compress more than 4GB
572
0
      uncompressed_data.size() > std::numeric_limits<uint32_t>::max() ||
573
      // Need enough output space for encoding uncompressed size
574
0
      compressed_output_size <= 5) {
575
    // Compression bypassed
576
0
    return {nullptr, 0};
577
0
  }
578
  // Standard format for prepending uncompressed size to the compressed
579
  // data in compress_format_version=2
580
0
  char* alg_output = EncodeVarint32(
581
0
      compressed_output, static_cast<uint32_t>(uncompressed_data.size()));
582
0
  size_t alg_max_output_size =
583
0
      compressed_output_size - (alg_output - compressed_output);
584
0
  return {alg_output, alg_max_output_size};
585
0
}
586
587
class BuiltinZlibCompressorV2 final : public CompressorWithSimpleDictBase {
588
 public:
589
  using CompressorWithSimpleDictBase::CompressorWithSimpleDictBase;
590
591
0
  const char* Name() const override { return "BuiltinZlibCompressorV2"; }
592
593
0
  CompressionType GetPreferredCompressionType() const override {
594
0
    return kZlibCompression;
595
0
  }
596
597
  std::unique_ptr<Compressor> CloneForDict(
598
0
      std::string&& dict_data) const override {
599
0
    return std::make_unique<BuiltinZlibCompressorV2>(opts_,
600
0
                                                     std::move(dict_data));
601
0
  }
602
603
  Status CompressBlock(Slice uncompressed_data, char* compressed_output,
604
                       size_t* compressed_output_size,
605
                       CompressionType* out_compression_type,
606
0
                       ManagedWorkingArea*) override {
607
0
#ifdef ZLIB
608
0
    auto [alg_output, alg_max_output_size] = StartCompressBlockV2(
609
0
        uncompressed_data, compressed_output, *compressed_output_size);
610
0
    if (alg_max_output_size == 0) {
611
      // Compression bypassed
612
0
      *compressed_output_size = 0;
613
0
      *out_compression_type = kNoCompression;
614
0
      return Status::OK();
615
0
    }
616
617
    // The memLevel parameter specifies how much memory should be allocated for
618
    // the internal compression state.
619
    // memLevel=1 uses minimum memory but is slow and reduces compression ratio.
620
    // memLevel=9 uses maximum memory for optimal speed.
621
    // The default value is 8. See zconf.h for more details.
622
0
    static const int memLevel = 8;
623
0
    int level = opts_.level;
624
0
    if (level == CompressionOptions::kDefaultCompressionLevel) {
625
0
      level = Z_DEFAULT_COMPRESSION;
626
0
    }
627
628
0
    z_stream stream;
629
0
    memset(&stream, 0, sizeof(z_stream));
630
631
    // Initialize the zlib stream
632
0
    int st = deflateInit2(&stream, level, Z_DEFLATED, opts_.window_bits,
633
0
                          memLevel, opts_.strategy);
634
0
    if (st != Z_OK) {
635
0
      *compressed_output_size = 0;
636
0
      *out_compression_type = kNoCompression;
637
0
      return Status::OK();
638
0
    }
639
640
    // Set dictionary if available
641
0
    if (!dict_data_.empty()) {
642
0
      st = deflateSetDictionary(
643
0
          &stream, reinterpret_cast<const Bytef*>(dict_data_.data()),
644
0
          static_cast<unsigned int>(dict_data_.size()));
645
0
      if (st != Z_OK) {
646
0
        deflateEnd(&stream);
647
0
        *compressed_output_size = 0;
648
0
        *out_compression_type = kNoCompression;
649
0
        return Status::OK();
650
0
      }
651
0
    }
652
653
    // Set up input
654
0
    stream.next_in = (Bytef*)uncompressed_data.data();
655
0
    stream.avail_in = static_cast<unsigned int>(uncompressed_data.size());
656
657
    // Set up output
658
0
    stream.next_out = reinterpret_cast<Bytef*>(alg_output);
659
0
    stream.avail_out = static_cast<unsigned int>(alg_max_output_size);
660
661
    // Compress
662
0
    st = deflate(&stream, Z_FINISH);
663
0
    size_t outlen = alg_max_output_size - stream.avail_out;
664
0
    deflateEnd(&stream);
665
666
0
    if (st == Z_STREAM_END) {
667
      // Compression kept/successful
668
0
      *compressed_output_size =
669
0
          outlen + /*header size*/ (alg_output - compressed_output);
670
0
      *out_compression_type = kZlibCompression;
671
0
      return Status::OK();
672
0
    }
673
    // Compression failed or rejected
674
0
    *compressed_output_size = 1;
675
#else
676
    (void)uncompressed_data;
677
    (void)compressed_output;
678
    // Compression bypassed (not supported)
679
    *compressed_output_size = 0;
680
#endif
681
0
    *out_compression_type = kNoCompression;
682
0
    return Status::OK();
683
0
  }
684
};
685
686
class BuiltinBZip2CompressorV2 final : public CompressorWithSimpleDictBase {
687
 public:
688
  using CompressorWithSimpleDictBase::CompressorWithSimpleDictBase;
689
690
0
  const char* Name() const override { return "BuiltinBZip2CompressorV2"; }
691
692
0
  CompressionType GetPreferredCompressionType() const override {
693
0
    return kBZip2Compression;
694
0
  }
695
696
  std::unique_ptr<Compressor> CloneForDict(
697
0
      std::string&& dict_data) const override {
698
0
    return std::make_unique<BuiltinBZip2CompressorV2>(opts_,
699
0
                                                      std::move(dict_data));
700
0
  }
701
702
  Status CompressBlock(Slice uncompressed_data, char* compressed_output,
703
                       size_t* compressed_output_size,
704
                       CompressionType* out_compression_type,
705
0
                       ManagedWorkingArea*) override {
706
0
#ifdef BZIP2
707
0
    auto [alg_output, alg_max_output_size] = StartCompressBlockV2(
708
0
        uncompressed_data, compressed_output, *compressed_output_size);
709
0
    if (alg_max_output_size == 0) {
710
      // Compression bypassed
711
0
      *compressed_output_size = 0;
712
0
      *out_compression_type = kNoCompression;
713
0
      return Status::OK();
714
0
    }
715
716
    // BZip2 doesn't actually use the dictionary, but we store it for
717
    // compatibility similar to BuiltinSnappyCompressorV2
718
719
    // Initialize the bzip2 stream
720
0
    bz_stream stream;
721
0
    memset(&stream, 0, sizeof(bz_stream));
722
723
    // Block size 1 is 100K.
724
    // 0 is for silent.
725
    // 30 is the default workFactor
726
0
    int st = BZ2_bzCompressInit(&stream, 1, 0, 30);
727
0
    if (st != BZ_OK) {
728
0
      *compressed_output_size = 0;
729
0
      *out_compression_type = kNoCompression;
730
0
      return Status::OK();
731
0
    }
732
733
    // Set up input
734
0
    stream.next_in = const_cast<char*>(uncompressed_data.data());
735
0
    stream.avail_in = static_cast<unsigned int>(uncompressed_data.size());
736
737
    // Set up output
738
0
    stream.next_out = alg_output;
739
0
    stream.avail_out = static_cast<unsigned int>(alg_max_output_size);
740
741
    // Compress
742
0
    st = BZ2_bzCompress(&stream, BZ_FINISH);
743
0
    size_t outlen = alg_max_output_size - stream.avail_out;
744
0
    BZ2_bzCompressEnd(&stream);
745
746
    // Check for success
747
0
    if (st == BZ_STREAM_END) {
748
      // Compression kept/successful
749
0
      *compressed_output_size = outlen + (alg_output - compressed_output);
750
0
      *out_compression_type = kBZip2Compression;
751
0
      return Status::OK();
752
0
    }
753
    // Compression failed or rejected
754
0
    *compressed_output_size = 1;
755
#else
756
    (void)uncompressed_data;
757
    (void)compressed_output;
758
    // Compression bypassed (not supported)
759
    *compressed_output_size = 0;
760
#endif
761
0
    *out_compression_type = kNoCompression;
762
0
    return Status::OK();
763
0
  }
764
};
765
766
class BuiltinLZ4CompressorV2WithDict : public CompressorWithSimpleDictBase {
767
 public:
768
  using CompressorWithSimpleDictBase::CompressorWithSimpleDictBase;
769
770
0
  const char* Name() const override { return "BuiltinLZ4CompressorV2"; }
771
772
0
  CompressionType GetPreferredCompressionType() const override {
773
0
    return kLZ4Compression;
774
0
  }
775
776
  // LZ4 (accelerated, not LZ4HC) is more often SLOWER with parallel
777
  // compression than faster.
778
0
  uint32_t GetRecommendedParallelThreads() const override { return 1; }
779
780
  std::unique_ptr<Compressor> CloneForDict(
781
0
      std::string&& dict_data) const override {
782
0
    return std::make_unique<BuiltinLZ4CompressorV2WithDict>(
783
0
        opts_, std::move(dict_data));
784
0
  }
785
786
0
  ManagedWorkingArea ObtainWorkingArea() override {
787
#ifdef LZ4
788
    return {reinterpret_cast<WorkingArea*>(LZ4_createStream()), this};
789
#else
790
0
    return {};
791
0
#endif
792
0
  }
793
0
  void ReleaseWorkingArea(WorkingArea* wa) override {
794
0
    if (wa) {
795
#ifdef LZ4
796
      LZ4_freeStream(reinterpret_cast<LZ4_stream_t*>(wa));
797
#endif
798
0
    }
799
0
  }
800
801
  Status CompressBlock(Slice uncompressed_data, char* compressed_output,
802
                       size_t* compressed_output_size,
803
                       CompressionType* out_compression_type,
804
0
                       ManagedWorkingArea* wa) override {
805
#ifdef LZ4
806
    auto [alg_output, alg_max_output_size] = StartCompressBlockV2(
807
        uncompressed_data, compressed_output, *compressed_output_size);
808
    if (alg_max_output_size == 0) {
809
      // Compression bypassed
810
      *compressed_output_size = 0;
811
      *out_compression_type = kNoCompression;
812
      return Status::OK();
813
    }
814
815
    ManagedWorkingArea tmp_wa;
816
    LZ4_stream_t* stream;
817
    if (wa != nullptr && wa->owner() == this) {
818
      stream = reinterpret_cast<LZ4_stream_t*>(wa->get());
819
#if LZ4_VERSION_NUMBER >= 10900  // >= version 1.9.0
820
      LZ4_resetStream_fast(stream);
821
#else
822
      LZ4_resetStream(stream);
823
#endif
824
    } else {
825
      tmp_wa = ObtainWorkingArea();
826
      stream = reinterpret_cast<LZ4_stream_t*>(tmp_wa.get());
827
    }
828
    if (!dict_data_.empty()) {
829
      // TODO: more optimization possible here?
830
      LZ4_loadDict(stream, dict_data_.data(),
831
                   static_cast<int>(dict_data_.size()));
832
    }
833
    int acceleration = LZ4AccelerationFromLevel(opts_.level);
834
    auto outlen = LZ4_compress_fast_continue(
835
        stream, uncompressed_data.data(), alg_output,
836
        static_cast<int>(uncompressed_data.size()),
837
        static_cast<int>(alg_max_output_size), acceleration);
838
    if (outlen > 0) {
839
      // Compression kept/successful
840
      size_t output_size = static_cast<size_t>(
841
          outlen + /*header size*/ (alg_output - compressed_output));
842
      assert(output_size <= *compressed_output_size);
843
      *compressed_output_size = output_size;
844
      *out_compression_type = kLZ4Compression;
845
      return Status::OK();
846
    }
847
    // Compression rejected
848
    *compressed_output_size = 1;
849
#else
850
0
    (void)uncompressed_data;
851
0
    (void)compressed_output;
852
0
    (void)wa;
853
    // Compression bypassed (not supported)
854
0
    *compressed_output_size = 0;
855
0
#endif
856
0
    *out_compression_type = kNoCompression;
857
0
    return Status::OK();
858
0
  }
859
860
 protected:
861
  // Translates a non-positive `level` to an LZ4 "acceleration" value (=
862
  // -level), clamped to the maximum effective acceleration. The clamp avoids
863
  // signed overflow when negating INT_MIN and reflects that lz4 internally caps
864
  // acceleration at LZ4_ACCELERATION_MAX (currently 65537; not exposed by
865
  // lz4.h).
866
0
  [[maybe_unused]] static int LZ4AccelerationFromLevel(int level) {
867
0
    assert(level <= 0 || level == CompressionOptions::kDefaultCompressionLevel);
868
0
    constexpr int kLZ4MaxAcceleration = 65537;  // == LZ4_ACCELERATION_MAX
869
0
    if (level <= -kLZ4MaxAcceleration) {
870
0
      return kLZ4MaxAcceleration;
871
0
    }
872
0
    if (level >= 0) {
873
0
      return 1;
874
0
    }
875
0
    return -level;
876
0
  }
877
};
878
879
class BuiltinLZ4CompressorV2NoDict final
880
    : public BuiltinLZ4CompressorV2WithDict {
881
 public:
882
  explicit BuiltinLZ4CompressorV2NoDict(const CompressionOptions& opts)
883
0
      : BuiltinLZ4CompressorV2WithDict(opts, /*dict_data=*/{}) {}
884
885
0
  std::unique_ptr<Compressor> Clone() const override {
886
0
    return std::make_unique<BuiltinLZ4CompressorV2NoDict>(opts_);
887
0
  }
888
889
0
  ManagedWorkingArea ObtainWorkingArea() override {
890
    // Using an LZ4_stream_t between compressions and resetting with
891
    // LZ4_resetStream_fast is actually slower than using a fresh LZ4_stream_t
892
    // each time, or not involving a stream at all. Similarly, using an extState
893
    // does not seem to offer a performance boost, perhaps a small regression.
894
0
    return {};
895
0
  }
896
897
0
  void ReleaseWorkingArea(WorkingArea* wa) override {
898
    // Should not be called
899
0
    (void)wa;
900
0
    assert(wa == nullptr);
901
0
  }
902
903
  Status CompressBlock(Slice uncompressed_data, char* compressed_output,
904
                       size_t* compressed_output_size,
905
                       CompressionType* out_compression_type,
906
0
                       ManagedWorkingArea* wa) override {
907
#ifdef LZ4
908
    (void)wa;
909
    auto [alg_output, alg_max_output_size] = StartCompressBlockV2(
910
        uncompressed_data, compressed_output, *compressed_output_size);
911
    if (alg_max_output_size == 0) {
912
      // Compression bypassed
913
      *compressed_output_size = 0;
914
      *out_compression_type = kNoCompression;
915
      return Status::OK();
916
    }
917
    int acceleration = LZ4AccelerationFromLevel(opts_.level);
918
    auto outlen =
919
        LZ4_compress_fast(uncompressed_data.data(), alg_output,
920
                          static_cast<int>(uncompressed_data.size()),
921
                          static_cast<int>(alg_max_output_size), acceleration);
922
    if (outlen > 0) {
923
      // Compression kept/successful
924
      size_t output_size = static_cast<size_t>(
925
          outlen + /*header size*/ (alg_output - compressed_output));
926
      assert(output_size <= *compressed_output_size);
927
      *compressed_output_size = output_size;
928
      *out_compression_type = kLZ4Compression;
929
      return Status::OK();
930
    }
931
    // Compression rejected
932
    *compressed_output_size = 1;
933
#else
934
0
    (void)uncompressed_data;
935
0
    (void)compressed_output;
936
0
    (void)wa;
937
    // Compression bypassed (not supported)
938
0
    *compressed_output_size = 0;
939
0
#endif
940
0
    *out_compression_type = kNoCompression;
941
0
    return Status::OK();
942
0
  }
943
};
944
945
class BuiltinLZ4HCCompressorV2 final : public CompressorWithSimpleDictBase {
946
 public:
947
  using CompressorWithSimpleDictBase::CompressorWithSimpleDictBase;
948
949
0
  const char* Name() const override { return "BuiltinLZ4HCCompressorV2"; }
950
951
0
  CompressionType GetPreferredCompressionType() const override {
952
0
    return kLZ4HCCompression;
953
0
  }
954
955
  std::unique_ptr<Compressor> CloneForDict(
956
0
      std::string&& dict_data) const override {
957
0
    return std::make_unique<BuiltinLZ4HCCompressorV2>(opts_,
958
0
                                                      std::move(dict_data));
959
0
  }
960
961
0
  ManagedWorkingArea ObtainWorkingArea() override {
962
#ifdef LZ4
963
    return {reinterpret_cast<WorkingArea*>(LZ4_createStreamHC()), this};
964
#else
965
0
    return {};
966
0
#endif
967
0
  }
968
0
  void ReleaseWorkingArea(WorkingArea* wa) override {
969
0
    if (wa) {
970
#ifdef LZ4
971
      LZ4_freeStreamHC(reinterpret_cast<LZ4_streamHC_t*>(wa));
972
#endif
973
0
    }
974
0
  }
975
976
  Status CompressBlock(Slice uncompressed_data, char* compressed_output,
977
                       size_t* compressed_output_size,
978
                       CompressionType* out_compression_type,
979
0
                       ManagedWorkingArea* wa) override {
980
#ifdef LZ4
981
    auto [alg_output, alg_max_output_size] = StartCompressBlockV2(
982
        uncompressed_data, compressed_output, *compressed_output_size);
983
    if (alg_max_output_size == 0) {
984
      // Compression bypassed
985
      *compressed_output_size = 0;
986
      *out_compression_type = kNoCompression;
987
      return Status::OK();
988
    }
989
990
    int level = opts_.level;
991
    if (level == CompressionOptions::kDefaultCompressionLevel) {
992
      level = 0;  // lz4hc.h says any value < 1 will be sanitized to default
993
    } else if (level > LZ4HC_CLEVEL_MAX) {
994
      // Map large positive levels to the maximum effective level. lz4hc clamps
995
      // internally too, but make it explicit for clarity/determinism.
996
      level = LZ4HC_CLEVEL_MAX;
997
    }
998
999
    ManagedWorkingArea tmp_wa;
1000
    LZ4_streamHC_t* stream;
1001
    if (wa != nullptr && wa->owner() == this) {
1002
      stream = reinterpret_cast<LZ4_streamHC_t*>(wa->get());
1003
    } else {
1004
      tmp_wa = ObtainWorkingArea();
1005
      stream = reinterpret_cast<LZ4_streamHC_t*>(tmp_wa.get());
1006
    }
1007
#if LZ4_VERSION_NUMBER >= 10900  // >= version 1.9.0
1008
    LZ4_resetStreamHC_fast(stream, level);
1009
#else
1010
    LZ4_resetStreamHC(stream, level);
1011
#endif
1012
    if (dict_data_.size() > 0) {
1013
      // TODO: more optimization possible here?
1014
      LZ4_loadDictHC(stream, dict_data_.data(),
1015
                     static_cast<int>(dict_data_.size()));
1016
    }
1017
1018
    auto outlen =
1019
        LZ4_compress_HC_continue(stream, uncompressed_data.data(), alg_output,
1020
                                 static_cast<int>(uncompressed_data.size()),
1021
                                 static_cast<int>(alg_max_output_size));
1022
    if (outlen > 0) {
1023
      // Compression kept/successful
1024
      size_t output_size = static_cast<size_t>(
1025
          outlen + /*header size*/ (alg_output - compressed_output));
1026
      assert(output_size <= *compressed_output_size);
1027
      *compressed_output_size = output_size;
1028
      *out_compression_type = kLZ4HCCompression;
1029
      return Status::OK();
1030
    }
1031
    // Compression rejected
1032
    *compressed_output_size = 1;
1033
#else
1034
0
    (void)uncompressed_data;
1035
0
    (void)compressed_output;
1036
0
    (void)wa;
1037
    // Compression bypassed (not supported)
1038
0
    *compressed_output_size = 0;
1039
0
#endif
1040
0
    *out_compression_type = kNoCompression;
1041
0
    return Status::OK();
1042
0
  }
1043
};
1044
1045
class BuiltinXpressCompressorV2 final : public CompressorWithSimpleDictBase {
1046
 public:
1047
  using CompressorWithSimpleDictBase::CompressorWithSimpleDictBase;
1048
1049
0
  const char* Name() const override { return "BuiltinXpressCompressorV2"; }
1050
1051
0
  CompressionType GetPreferredCompressionType() const override {
1052
0
    return kXpressCompression;
1053
0
  }
1054
1055
  std::unique_ptr<Compressor> CloneForDict(
1056
0
      std::string&& dict_data) const override {
1057
0
    return std::make_unique<BuiltinXpressCompressorV2>(opts_,
1058
0
                                                       std::move(dict_data));
1059
0
  }
1060
1061
  Status CompressBlock(Slice uncompressed_data, char* compressed_output,
1062
                       size_t* compressed_output_size,
1063
                       CompressionType* out_compression_type,
1064
0
                       ManagedWorkingArea*) override {
1065
#ifdef XPRESS
1066
    // XPRESS doesn't actually use the dictionary, but we store it for
1067
    // compatibility similar to BuiltinSnappyCompressorV2
1068
1069
    // Use the new CompressWithMaxSize function that writes directly to the
1070
    // output buffer
1071
    size_t compressed_size = port::xpress::CompressWithMaxSize(
1072
        uncompressed_data.data(), uncompressed_data.size(), compressed_output,
1073
        *compressed_output_size);
1074
1075
    if (compressed_size > 0) {
1076
      // Compression kept/successful
1077
      *compressed_output_size = compressed_size;
1078
      *out_compression_type = kXpressCompression;
1079
      return Status::OK();
1080
    }
1081
1082
    // Compression rejected or failed
1083
    *compressed_output_size = 1;
1084
#else
1085
0
    (void)uncompressed_data;
1086
0
    (void)compressed_output;
1087
    // Compression bypassed (not supported)
1088
0
    *compressed_output_size = 0;
1089
0
#endif
1090
0
    *out_compression_type = kNoCompression;
1091
0
    return Status::OK();
1092
0
  }
1093
};
1094
1095
class BuiltinZSTDCompressorV2 final : public CompressorBase {
1096
 public:
1097
  explicit BuiltinZSTDCompressorV2(const CompressionOptions& opts,
1098
                                   CompressionDict&& dict = {})
1099
0
      : CompressorBase(opts), dict_(std::move(dict)) {}
1100
1101
0
  const char* Name() const override { return "BuiltinZSTDCompressorV2"; }
1102
1103
0
  CompressionType GetPreferredCompressionType() const override { return kZSTD; }
1104
1105
0
  uint32_t GetRecommendedParallelThreads() const override {
1106
    // Accelerated ZSTD levels could see a small throughput increase with
1107
    // parallel compression, but the overall CPU overhead is generally not worth
1108
    // it.
1109
0
    return opts_.level < 0 ? 1
1110
0
                           : CompressorBase::GetRecommendedParallelThreads();
1111
0
  }
1112
1113
0
  std::unique_ptr<Compressor> Clone() const override {
1114
0
    CompressionDict dict_copy{dict_.GetRawDict().ToString(), kZSTD,
1115
0
                              opts_.level};
1116
0
    return std::make_unique<BuiltinZSTDCompressorV2>(opts_,
1117
0
                                                     std::move(dict_copy));
1118
0
  }
1119
1120
0
  DictConfig GetDictGuidance(CacheEntryRole /*block_type*/) const override {
1121
0
    if (opts_.max_dict_bytes == 0) {
1122
      // Dictionary compression disabled
1123
0
      return DictDisabled{};
1124
0
    } else {
1125
0
      size_t max_sample_bytes = opts_.zstd_max_train_bytes > 0
1126
0
                                    ? opts_.zstd_max_train_bytes
1127
0
                                    : opts_.max_dict_bytes;
1128
0
      return DictSampling{max_sample_bytes};
1129
0
    }
1130
0
  }
1131
1132
  // NOTE: empty dict is equivalent to no dict
1133
0
  Slice GetSerializedDict() const override { return dict_.GetRawDict(); }
1134
1135
0
  ManagedWorkingArea ObtainWorkingArea() override {
1136
#ifdef ZSTD
1137
    ZSTD_CCtx* ctx =
1138
#ifdef ROCKSDB_ZSTD_CUSTOM_MEM
1139
        ZSTD_createCCtx_advanced(port::GetJeZstdAllocationOverrides());
1140
#else   // ROCKSDB_ZSTD_CUSTOM_MEM
1141
        ZSTD_createCCtx();
1142
#endif  // ROCKSDB_ZSTD_CUSTOM_MEM
1143
    int level = SanitizeZSTDCompressionLevel(opts_.level);
1144
    size_t err = ZSTD_CCtx_setParameter(ctx, ZSTD_c_compressionLevel, level);
1145
    if (ZSTD_isError(err)) {
1146
      assert(false);
1147
      ZSTD_freeCCtx(ctx);
1148
      ctx = ZSTD_createCCtx();
1149
    }
1150
    if (opts_.checksum) {
1151
      err = ZSTD_CCtx_setParameter(ctx, ZSTD_c_checksumFlag, 1);
1152
      if (ZSTD_isError(err)) {
1153
        assert(false);
1154
        ZSTD_freeCCtx(ctx);
1155
        ctx = ZSTD_createCCtx();
1156
      }
1157
    }
1158
    return ManagedWorkingArea(reinterpret_cast<WorkingArea*>(ctx), this);
1159
#else
1160
0
    return {};
1161
0
#endif  // ZSTD
1162
0
  }
1163
1164
0
  void ReleaseWorkingArea(WorkingArea* wa) override {
1165
0
    if (wa) {
1166
#ifdef ZSTD
1167
      ZSTD_freeCCtx(reinterpret_cast<ZSTD_CCtx*>(wa));
1168
#endif  // ZSTD
1169
0
    }
1170
0
  }
1171
1172
  Status CompressBlock(Slice uncompressed_data, char* compressed_output,
1173
                       size_t* compressed_output_size,
1174
                       CompressionType* out_compression_type,
1175
0
                       ManagedWorkingArea* wa) override {
1176
#ifdef ZSTD
1177
    auto [alg_output, alg_max_output_size] = StartCompressBlockV2(
1178
        uncompressed_data, compressed_output, *compressed_output_size);
1179
    if (alg_max_output_size == 0) {
1180
      // Compression bypassed
1181
      *compressed_output_size = 0;
1182
      *out_compression_type = kNoCompression;
1183
      return Status::OK();
1184
    }
1185
1186
    ManagedWorkingArea tmp_wa;
1187
    if (wa == nullptr || wa->owner() != this) {
1188
      tmp_wa = ObtainWorkingArea();
1189
      wa = &tmp_wa;
1190
    }
1191
    assert(wa->get() != nullptr);
1192
    ZSTD_CCtx* ctx = reinterpret_cast<ZSTD_CCtx*>(wa->get());
1193
1194
    if (dict_.GetDigestedZstdCDict() != nullptr) {
1195
      ZSTD_CCtx_refCDict(ctx, dict_.GetDigestedZstdCDict());
1196
    } else {
1197
      ZSTD_CCtx_loadDictionary(ctx, dict_.GetRawDict().data(),
1198
                               dict_.GetRawDict().size());
1199
    }
1200
1201
    // Compression level is set in `contex` during ObtainWorkingArea()
1202
    size_t outlen =
1203
        ZSTD_compress2(ctx, alg_output, alg_max_output_size,
1204
                       uncompressed_data.data(), uncompressed_data.size());
1205
    if (!ZSTD_isError(outlen)) {
1206
      // Compression kept/successful
1207
      size_t output_size = static_cast<size_t>(
1208
          outlen + /*header size*/ (alg_output - compressed_output));
1209
      assert(output_size <= *compressed_output_size);
1210
      *compressed_output_size = output_size;
1211
      *out_compression_type = kZSTD;
1212
      return Status::OK();
1213
    }
1214
    if (ZSTD_getErrorCode(outlen) != ZSTD_error_dstSize_tooSmall) {
1215
      return Status::Corruption(std::string("ZSTD_compress2 failed: ") +
1216
                                ZSTD_getErrorName(outlen));
1217
    }
1218
    // Compression rejected
1219
    *compressed_output_size = 1;
1220
#else
1221
0
    (void)uncompressed_data;
1222
0
    (void)compressed_output;
1223
0
    (void)wa;
1224
    // Compression bypassed (not supported)
1225
0
    *compressed_output_size = 0;
1226
0
#endif
1227
0
    *out_compression_type = kNoCompression;
1228
0
    return Status::OK();
1229
0
  }
1230
1231
  std::unique_ptr<Compressor> MaybeCloneSpecialized(
1232
      CacheEntryRole /*block_type*/,
1233
0
      DictConfigArgs&& dict_config) const override {
1234
    // Handle DictDisabled
1235
    // TODO: use holds_alternative
1236
0
    if (auto* disabled = std::get_if<DictDisabled>(&dict_config)) {
1237
0
      (void)disabled;
1238
0
      return nullptr;
1239
0
    }
1240
1241
0
    std::string dict_data;
1242
1243
    // Handle DictPreDefined - use the pre-defined dictionary directly
1244
0
    if (auto* predef = std::get_if<DictPreDefined>(&dict_config)) {
1245
0
      if (predef->dict_data.empty()) {
1246
0
        return nullptr;
1247
0
      }
1248
0
      dict_data = std::move(predef->dict_data);
1249
0
    }
1250
1251
    // Handle DictSamples - train dictionary from samples
1252
0
    if (auto* samples = std::get_if<DictSamples>(&dict_config)) {
1253
0
      assert(samples->Verify());
1254
0
      if (samples->empty()) {
1255
0
        return nullptr;
1256
0
      }
1257
      // Migrated from BlockBasedTableBuilder::EnterUnbuffered()
1258
0
      if (opts_.zstd_max_train_bytes > 0) {
1259
0
        assert(samples->sample_data.size() <= opts_.zstd_max_train_bytes);
1260
0
        if (opts_.use_zstd_dict_trainer) {
1261
0
          dict_data = ZSTD_TrainDictionary(
1262
0
              samples->sample_data, samples->sample_lens, opts_.max_dict_bytes);
1263
0
        } else {
1264
0
          dict_data = ZSTD_FinalizeDictionary(
1265
0
              samples->sample_data, samples->sample_lens, opts_.max_dict_bytes,
1266
0
              opts_.level);
1267
0
        }
1268
0
      } else {
1269
0
        assert(samples->sample_data.size() <= opts_.max_dict_bytes);
1270
        // ZSTD "raw content dictionary" - "Any buffer is a valid raw content
1271
        // dictionary." Or similar for other compressions.
1272
0
        dict_data = std::move(samples->sample_data);
1273
0
      }
1274
0
    }
1275
1276
0
    CompressionDict dict{std::move(dict_data), kZSTD, opts_.level};
1277
0
    return std::make_unique<BuiltinZSTDCompressorV2>(opts_, std::move(dict));
1278
0
  }
1279
1280
  std::shared_ptr<Decompressor> GetOptimizedDecompressor() const override;
1281
1282
 protected:
1283
  const CompressionDict dict_;
1284
};
1285
1286
// Subroutines for BuiltinDecompressorV2
1287
1288
Status Snappy_DecompressBlock(const Decompressor::Args& args,
1289
0
                              char* uncompressed_output) {
1290
#ifdef SNAPPY
1291
  if (!snappy::RawUncompress(args.compressed_data.data(),
1292
                             args.compressed_data.size(),
1293
                             uncompressed_output)) {
1294
    return Status::Corruption("Error decompressing snappy data");
1295
  }
1296
  return Status::OK();
1297
#else
1298
0
  (void)args;
1299
0
  (void)uncompressed_output;
1300
0
  return Status::NotSupported("Snappy not supported in this build");
1301
0
#endif
1302
0
}
1303
1304
Status Zlib_DecompressBlock(const Decompressor::Args& args, Slice dict,
1305
0
                            char* uncompressed_output) {
1306
0
#ifdef ZLIB
1307
  // NOTE: uses "raw" format
1308
0
  constexpr int kWindowBits = -14;
1309
1310
0
  z_stream _stream;
1311
0
  memset(&_stream, 0, sizeof(z_stream));
1312
1313
  // For raw inflate, the windowBits should be -8..-15.
1314
  // If windowBits is bigger than zero, it will use either zlib
1315
  // header or gzip header. Adding 32 to it will do automatic detection.
1316
0
  int st = inflateInit2(&_stream, kWindowBits);
1317
0
  if (UNLIKELY(st != Z_OK)) {
1318
0
    return Status::Corruption("Failed to initialize zlib inflate: " +
1319
0
                              std::to_string(st));
1320
0
  }
1321
1322
0
  if (!dict.empty()) {
1323
    // Initialize the compression library's dictionary
1324
0
    st = inflateSetDictionary(&_stream,
1325
0
                              reinterpret_cast<const Bytef*>(dict.data()),
1326
0
                              static_cast<unsigned int>(dict.size()));
1327
0
    if (UNLIKELY(st != Z_OK)) {
1328
0
      return Status::Corruption("Failed to initialize zlib dictionary: " +
1329
0
                                std::to_string(st));
1330
0
    }
1331
0
  }
1332
1333
0
  _stream.next_in = const_cast<Bytef*>(
1334
0
      reinterpret_cast<const Bytef*>(args.compressed_data.data()));
1335
0
  _stream.avail_in = static_cast<unsigned int>(args.compressed_data.size());
1336
1337
0
  _stream.next_out = reinterpret_cast<Bytef*>(uncompressed_output);
1338
0
  _stream.avail_out = static_cast<unsigned int>(args.uncompressed_size);
1339
1340
0
  st = inflate(&_stream, Z_SYNC_FLUSH);
1341
0
  if (UNLIKELY(st != Z_STREAM_END)) {
1342
0
    inflateEnd(&_stream);
1343
    // NOTE: Z_OK is still corruption because it means we got the size wrong
1344
0
    return Status::Corruption("Failed zlib inflate: " + std::to_string(st));
1345
0
  }
1346
1347
  // We should have no bytes left
1348
0
  if (_stream.avail_out != 0) {
1349
0
    inflateEnd(&_stream);
1350
0
    return Status::Corruption("Size mismatch decompressing zlib data");
1351
0
  }
1352
1353
0
  inflateEnd(&_stream);
1354
0
  return Status::OK();
1355
#else
1356
  (void)args;
1357
  (void)dict;
1358
  (void)uncompressed_output;
1359
  return Status::NotSupported("Zlib not supported in this build");
1360
#endif
1361
0
}
1362
1363
Status BZip2_DecompressBlock(const Decompressor::Args& args,
1364
0
                             char* uncompressed_output) {
1365
0
#ifdef BZIP2
1366
0
  auto uncompressed_size = static_cast<unsigned int>(args.uncompressed_size);
1367
0
  if (BZ_OK != BZ2_bzBuffToBuffDecompress(
1368
0
                   uncompressed_output, &uncompressed_size,
1369
0
                   const_cast<char*>(args.compressed_data.data()),
1370
0
                   static_cast<unsigned int>(args.compressed_data.size()),
1371
0
                   0 /*small mem*/, 0 /*verbosity*/)) {
1372
0
    return Status::Corruption("Error decompressing bzip2 data");
1373
0
  }
1374
0
  if (uncompressed_size != args.uncompressed_size) {
1375
0
    return Status::Corruption("Size mismatch decompressing bzip2 data");
1376
0
  }
1377
0
  return Status::OK();
1378
#else
1379
  (void)args;
1380
  (void)uncompressed_output;
1381
  return Status::NotSupported("BZip2 not supported in this build");
1382
#endif
1383
0
}
1384
1385
Status LZ4_DecompressBlock(const Decompressor::Args& args, Slice dict,
1386
0
                           char* uncompressed_output) {
1387
#ifdef LZ4
1388
  int expected_uncompressed_size = static_cast<int>(args.uncompressed_size);
1389
  LZ4_streamDecode_t* stream = LZ4_createStreamDecode();
1390
  if (!dict.empty()) {
1391
    LZ4_setStreamDecode(stream, dict.data(), static_cast<int>(dict.size()));
1392
  }
1393
  int uncompressed_size = LZ4_decompress_safe_continue(
1394
      stream, args.compressed_data.data(), uncompressed_output,
1395
      static_cast<int>(args.compressed_data.size()),
1396
      expected_uncompressed_size);
1397
  LZ4_freeStreamDecode(stream);
1398
1399
  if (uncompressed_size != expected_uncompressed_size) {
1400
    if (uncompressed_size < 0) {
1401
      return Status::Corruption("Error decompressing LZ4 data");
1402
    } else {
1403
      return Status::Corruption("Size mismatch decompressing LZ4 data");
1404
    }
1405
  }
1406
  return Status::OK();
1407
#else
1408
0
  (void)args;
1409
0
  (void)dict;
1410
0
  (void)uncompressed_output;
1411
0
  return Status::NotSupported("LZ4 not supported in this build");
1412
0
#endif
1413
0
}
1414
1415
Status XPRESS_DecompressBlock(const Decompressor::Args& args,
1416
0
                              char* uncompressed_output) {
1417
#ifdef XPRESS
1418
  int64_t actual_uncompressed_size = port::xpress::DecompressToBuffer(
1419
      args.compressed_data.data(), args.compressed_data.size(),
1420
      uncompressed_output, args.uncompressed_size);
1421
  if (actual_uncompressed_size !=
1422
      static_cast<int64_t>(args.uncompressed_size)) {
1423
    if (actual_uncompressed_size < 0) {
1424
      return Status::Corruption("Error decompressing XPRESS data");
1425
    } else {
1426
      return Status::Corruption("Size mismatch decompressing XPRESS data");
1427
    }
1428
  }
1429
  return Status::OK();
1430
#else
1431
0
  (void)args;
1432
0
  (void)uncompressed_output;
1433
0
  return Status::NotSupported("XPRESS not supported in this build");
1434
0
#endif
1435
0
}
1436
1437
template <bool kIsDigestedDict = false>
1438
Status ZSTD_DecompressBlockWithContext(
1439
    const Decompressor::Args& args,
1440
    std::conditional_t<kIsDigestedDict, void*, Slice> dict,
1441
    ZSTDUncompressCachedData::ZSTDNativeContext zstd_context,
1442
0
    char* uncompressed_output) {
1443
#ifdef ZSTD
1444
  size_t uncompressed_size;
1445
  assert(zstd_context != nullptr);
1446
  if constexpr (kIsDigestedDict) {
1447
#ifdef ROCKSDB_ZSTD_DDICT
1448
    uncompressed_size = ZSTD_decompress_usingDDict(
1449
        zstd_context, uncompressed_output, args.uncompressed_size,
1450
        args.compressed_data.data(), args.compressed_data.size(),
1451
        static_cast<ZSTD_DDict*>(dict));
1452
#else
1453
    static_assert(!kIsDigestedDict,
1454
                  "Inconsistent expectation of ZSTD digested dict support");
1455
#endif  // ROCKSDB_ZSTD_DDICT
1456
  } else if (dict.empty()) {
1457
    uncompressed_size = ZSTD_decompressDCtx(
1458
        zstd_context, uncompressed_output, args.uncompressed_size,
1459
        args.compressed_data.data(), args.compressed_data.size());
1460
  } else {
1461
    uncompressed_size = ZSTD_decompress_usingDict(
1462
        zstd_context, uncompressed_output, args.uncompressed_size,
1463
        args.compressed_data.data(), args.compressed_data.size(), dict.data(),
1464
        dict.size());
1465
  }
1466
  if (ZSTD_isError(uncompressed_size)) {
1467
    return Status::Corruption(std::string("ZSTD ") +
1468
                              ZSTD_getErrorName(uncompressed_size));
1469
  } else if (uncompressed_size != args.uncompressed_size) {
1470
    return Status::Corruption("ZSTD decompression size mismatch");
1471
  } else {
1472
    return Status::OK();
1473
  }
1474
#else
1475
0
  (void)args;
1476
0
  (void)dict;
1477
0
  (void)zstd_context;
1478
0
  (void)uncompressed_output;
1479
0
  return Status::NotSupported("ZSTD not supported in this build");
1480
0
#endif
1481
0
}
1482
1483
template <bool kIsDigestedDict = false>
1484
Status ZSTD_DecompressBlock(
1485
    const Decompressor::Args& args,
1486
    std::conditional_t<kIsDigestedDict, void*, Slice> dict,
1487
0
    const Decompressor* decompressor, char* uncompressed_output) {
1488
0
  if (args.working_area && args.working_area->owner() == decompressor) {
1489
0
    auto ctx = static_cast<UncompressionContext*>(args.working_area->get());
1490
0
    assert(ctx != nullptr);
1491
0
    if (ctx->GetZSTDContext() != nullptr) {
1492
0
      return ZSTD_DecompressBlockWithContext<kIsDigestedDict>(
1493
0
          args, dict, ctx->GetZSTDContext(), uncompressed_output);
1494
0
    }
1495
0
  }
1496
0
  UncompressionContext tmp_ctx{kZSTD};
1497
0
  return ZSTD_DecompressBlockWithContext<kIsDigestedDict>(
1498
0
      args, dict, tmp_ctx.GetZSTDContext(), uncompressed_output);
1499
0
}
1500
1501
class BuiltinDecompressorV2 : public Decompressor {
1502
 public:
1503
0
  const char* Name() const override { return "BuiltinDecompressorV2"; }
1504
1505
0
  Status ExtractUncompressedSize(Args& args) override {
1506
0
    assert(args.compression_type != kNoCompression);
1507
0
    if (args.compression_type == kSnappyCompression) {
1508
      // 1st exception to encoding of uncompressed size
1509
#ifdef SNAPPY
1510
      size_t uncompressed_length = 0;
1511
      if (!snappy::GetUncompressedLength(args.compressed_data.data(),
1512
                                         args.compressed_data.size(),
1513
                                         &uncompressed_length)) {
1514
        return Status::Corruption("Error reading snappy compressed length");
1515
      }
1516
      args.uncompressed_size = uncompressed_length;
1517
      return Status::OK();
1518
#else
1519
0
      return Status::NotSupported("Snappy not supported in this build");
1520
0
#endif
1521
0
    } else if (args.compression_type == kXpressCompression) {
1522
      // 2nd exception to encoding of uncompressed size
1523
#ifdef XPRESS
1524
      int64_t result = port::xpress::GetDecompressedSize(
1525
          args.compressed_data.data(), args.compressed_data.size());
1526
      if (result < 0) {
1527
        return Status::Corruption("Error reading XPRESS compressed length");
1528
      }
1529
      args.uncompressed_size = static_cast<size_t>(result);
1530
      return Status::OK();
1531
#else
1532
0
      return Status::NotSupported("XPRESS not supported in this build");
1533
0
#endif
1534
1535
0
    } else {
1536
      // Extract encoded uncompressed size
1537
0
      return Decompressor::ExtractUncompressedSize(args);
1538
0
    }
1539
0
  }
1540
1541
0
  Status DecompressBlock(const Args& args, char* uncompressed_output) override {
1542
0
    switch (args.compression_type) {
1543
0
      case kSnappyCompression:
1544
0
        return Snappy_DecompressBlock(args, uncompressed_output);
1545
0
      case kZlibCompression:
1546
0
        return Zlib_DecompressBlock(args, /*dict=*/Slice{},
1547
0
                                    uncompressed_output);
1548
0
      case kBZip2Compression:
1549
0
        return BZip2_DecompressBlock(args, uncompressed_output);
1550
0
      case kLZ4Compression:
1551
0
      case kLZ4HCCompression:
1552
0
        return LZ4_DecompressBlock(args, /*dict=*/Slice{}, uncompressed_output);
1553
0
      case kXpressCompression:
1554
0
        return XPRESS_DecompressBlock(args, uncompressed_output);
1555
0
      case kZSTD:
1556
0
        return ZSTD_DecompressBlock(args, /*dict=*/Slice{}, this,
1557
0
                                    uncompressed_output);
1558
0
      default:
1559
0
        return Status::NotSupported(
1560
0
            "Compression type not supported or not built-in: " +
1561
0
            CompressionTypeToString(args.compression_type));
1562
0
    }
1563
0
  }
1564
1565
  Status MaybeCloneForDict(const Slice&,
1566
                           std::unique_ptr<Decompressor>*) override;
1567
1568
0
  size_t ApproximateOwnedMemoryUsage() const override {
1569
0
    return sizeof(BuiltinDecompressorV2);
1570
0
  }
1571
};
1572
1573
class BuiltinDecompressorV2SnappyOnly final : public BuiltinDecompressorV2 {
1574
 public:
1575
0
  const char* Name() const override {
1576
0
    return "BuiltinDecompressorV2SnappyOnly";
1577
0
  }
1578
1579
0
  Status ExtractUncompressedSize(Args& args) override {
1580
0
    assert(args.compression_type == kSnappyCompression);
1581
#ifdef SNAPPY
1582
    size_t uncompressed_length = 0;
1583
    if (!snappy::GetUncompressedLength(args.compressed_data.data(),
1584
                                       args.compressed_data.size(),
1585
                                       &uncompressed_length)) {
1586
      return Status::Corruption("Error reading snappy compressed length");
1587
    }
1588
    args.uncompressed_size = uncompressed_length;
1589
    return Status::OK();
1590
#else
1591
0
    return Status::NotSupported("Snappy not supported in this build");
1592
0
#endif
1593
0
  }
1594
1595
0
  Status DecompressBlock(const Args& args, char* uncompressed_output) override {
1596
0
    assert(args.compression_type == kSnappyCompression);
1597
0
    return Snappy_DecompressBlock(args, uncompressed_output);
1598
0
  }
1599
};
1600
1601
class BuiltinDecompressorV2WithDict final : public BuiltinDecompressorV2 {
1602
 public:
1603
0
  explicit BuiltinDecompressorV2WithDict(const Slice& dict) : dict_(dict) {}
1604
1605
0
  const char* Name() const override { return "BuiltinDecompressorV2WithDict"; }
1606
1607
0
  Status DecompressBlock(const Args& args, char* uncompressed_output) override {
1608
0
    switch (args.compression_type) {
1609
0
      case kSnappyCompression:
1610
        // NOTE: quietly ignores the dictionary (for compatibility)
1611
0
        return Snappy_DecompressBlock(args, uncompressed_output);
1612
0
      case kZlibCompression:
1613
0
        return Zlib_DecompressBlock(args, dict_, uncompressed_output);
1614
0
      case kBZip2Compression:
1615
        // NOTE: quietly ignores the dictionary (for compatibility)
1616
0
        return BZip2_DecompressBlock(args, uncompressed_output);
1617
0
      case kLZ4Compression:
1618
0
      case kLZ4HCCompression:
1619
0
        return LZ4_DecompressBlock(args, dict_, uncompressed_output);
1620
0
      case kXpressCompression:
1621
        // NOTE: quietly ignores the dictionary (for compatibility)
1622
0
        return XPRESS_DecompressBlock(args, uncompressed_output);
1623
0
      case kZSTD:
1624
0
        return ZSTD_DecompressBlock(args, dict_, this, uncompressed_output);
1625
0
      default:
1626
0
        return Status::NotSupported(
1627
0
            "Compression type not supported or not built-in: " +
1628
0
            CompressionTypeToString(args.compression_type));
1629
0
    }
1630
0
  }
1631
1632
0
  const Slice& GetSerializedDict() const override { return dict_; }
1633
1634
0
  size_t ApproximateOwnedMemoryUsage() const override {
1635
0
    return sizeof(BuiltinDecompressorV2WithDict);
1636
0
  }
1637
1638
 protected:
1639
  const Slice dict_;
1640
};
1641
1642
Status BuiltinDecompressorV2::MaybeCloneForDict(
1643
0
    const Slice& dict, std::unique_ptr<Decompressor>* out) {
1644
  // Check RocksDB-promised precondition
1645
0
  assert(dict.size() > 0);
1646
  // Because of unfortunate decisions in handling built-in compression types,
1647
  // all the compression types before ZSTD that do not actually support
1648
  // dictionary compression pretend to support it. Specifically, we have to be
1649
  // able to read files with a compression dictionary block using those
1650
  // compression types even though the compression dictionary is ignored by
1651
  // the compression algorithm. And the Decompressor has to return the
1652
  // configured dictionary from GetSerializedDict() even if it is ignored. This
1653
  // unfortunately means that a new schema version (BuiltinV3?) would be needed
1654
  // toactually support dictionary compression in the future for these
1655
  // algorithms (if the libraries add support).
1656
  // TODO: can we make this a better/cleaner experience?
1657
0
  *out = std::make_unique<BuiltinDecompressorV2WithDict>(dict);
1658
0
  return Status::OK();
1659
0
}
1660
1661
class BuiltinDecompressorV2OptimizeZstd : public BuiltinDecompressorV2 {
1662
 public:
1663
0
  const char* Name() const override {
1664
0
    return "BuiltinDecompressorV2OptimizeZstd";
1665
0
  }
1666
1667
0
  ManagedWorkingArea ObtainWorkingArea(CompressionType preferred) override {
1668
0
    if (preferred == kZSTD) {
1669
      // TODO: evaluate whether it makes sense to use core local cache here.
1670
      // (Perhaps not, because explicit WorkingArea could be long-running.)
1671
0
      return ManagedWorkingArea(new UncompressionContext(kZSTD), this);
1672
0
    } else {
1673
0
      return {};
1674
0
    }
1675
0
  }
1676
1677
0
  void ReleaseWorkingArea(WorkingArea* wa) override {
1678
0
    delete static_cast<UncompressionContext*>(wa);
1679
0
  }
1680
1681
0
  Status DecompressBlock(const Args& args, char* uncompressed_output) override {
1682
0
    if (LIKELY(args.compression_type == kZSTD)) {
1683
0
      return ZSTD_DecompressBlock(args, /*dict=*/Slice{}, this,
1684
0
                                  uncompressed_output);
1685
0
    } else {
1686
0
      return BuiltinDecompressorV2::DecompressBlock(args, uncompressed_output);
1687
0
    }
1688
0
  }
1689
1690
  Status MaybeCloneForDict(const Slice& /*serialized_dict*/,
1691
                           std::unique_ptr<Decompressor>* /*out*/) override;
1692
};
1693
1694
class BuiltinDecompressorV2OptimizeZstdWithDict final
1695
    : public BuiltinDecompressorV2OptimizeZstd {
1696
 public:
1697
  explicit BuiltinDecompressorV2OptimizeZstdWithDict(const Slice& dict)
1698
      :
1699
#ifdef ROCKSDB_ZSTD_DDICT
1700
        dict_(dict),
1701
        ddict_(ZSTD_createDDict_byReference(dict.data(), dict.size())) {
1702
    assert(ddict_ != nullptr);
1703
  }
1704
#else
1705
0
        dict_(dict) {
1706
0
  }
1707
#endif  // ROCKSDB_ZSTD_DDICT
1708
1709
  // No copying allowed
1710
  BuiltinDecompressorV2OptimizeZstdWithDict(
1711
      const BuiltinDecompressorV2OptimizeZstdWithDict&) = delete;
1712
  BuiltinDecompressorV2OptimizeZstdWithDict& operator=(
1713
      const BuiltinDecompressorV2OptimizeZstdWithDict&) = delete;
1714
  BuiltinDecompressorV2OptimizeZstdWithDict(
1715
      BuiltinDecompressorV2OptimizeZstdWithDict&&) = delete;
1716
  BuiltinDecompressorV2OptimizeZstdWithDict& operator=(
1717
      BuiltinDecompressorV2OptimizeZstdWithDict&&) = delete;
1718
1719
0
  const char* Name() const override {
1720
0
    return "BuiltinDecompressorV2OptimizeZstdWithDict";
1721
0
  }
1722
1723
0
  ~BuiltinDecompressorV2OptimizeZstdWithDict() override {
1724
0
#ifdef ROCKSDB_ZSTD_DDICT
1725
0
    size_t res = ZSTD_freeDDict(ddict_);
1726
0
    assert(res == 0);  // Last I checked they can't fail
1727
0
    (void)res;         // prevent unused var warning
1728
0
#endif                 // ROCKSDB_ZSTD_DDICT
1729
0
  }
1730
1731
0
  const Slice& GetSerializedDict() const override { return dict_; }
1732
1733
0
  size_t ApproximateOwnedMemoryUsage() const override {
1734
0
    size_t sz = sizeof(BuiltinDecompressorV2WithDict);
1735
#ifdef ROCKSDB_ZSTD_DDICT
1736
    sz += ZSTD_sizeof_DDict(ddict_);
1737
#endif  // ROCKSDB_ZSTD_DDICT
1738
0
    return sz;
1739
0
  }
1740
1741
0
  Status DecompressBlock(const Args& args, char* uncompressed_output) override {
1742
0
    if (LIKELY(args.compression_type == kZSTD)) {
1743
#ifdef ROCKSDB_ZSTD_DDICT
1744
      return ZSTD_DecompressBlock</*kIsDigestedDict=*/true>(
1745
          args, ddict_, this, uncompressed_output);
1746
#else
1747
0
      return ZSTD_DecompressBlock(args, dict_, this, uncompressed_output);
1748
0
#endif  // ROCKSDB_ZSTD_DDICT
1749
0
    } else {
1750
0
      return BuiltinDecompressorV2WithDict(dict_).DecompressBlock(
1751
0
          args, uncompressed_output);
1752
0
    }
1753
0
  }
1754
1755
 protected:
1756
  const Slice dict_;
1757
#ifdef ROCKSDB_ZSTD_DDICT
1758
  ZSTD_DDict* const ddict_;
1759
#endif  // ROCKSDB_ZSTD_DDICT
1760
};
1761
1762
Status BuiltinDecompressorV2OptimizeZstd::MaybeCloneForDict(
1763
0
    const Slice& serialized_dict, std::unique_ptr<Decompressor>* out) {
1764
0
  *out = std::make_unique<BuiltinDecompressorV2OptimizeZstdWithDict>(
1765
0
      serialized_dict);
1766
0
  return Status::OK();
1767
0
}
1768
class BuiltinCompressionManagerV2 final : public CompressionManager {
1769
 public:
1770
4
  BuiltinCompressionManagerV2() = default;
1771
0
  ~BuiltinCompressionManagerV2() override = default;
1772
1773
0
  const char* Name() const override { return "BuiltinCompressionManagerV2"; }
1774
1775
0
  const char* CompatibilityName() const override { return "BuiltinV2"; }
1776
1777
  std::unique_ptr<Compressor> GetCompressor(const CompressionOptions& opts,
1778
19.6k
                                            CompressionType type) override {
1779
19.6k
    if (opts.max_compressed_bytes_per_kb <= 0) {
1780
      // No acceptable compression ratio => no compression
1781
0
      return nullptr;
1782
0
    }
1783
19.6k
    if (!SupportsCompressionType(type)) {
1784
      // Unrecognized or support not compiled in. Fall back on default
1785
0
      type = ColumnFamilyOptions{}.compression;
1786
0
    }
1787
19.6k
    switch (type) {
1788
19.6k
      case kNoCompression:
1789
19.6k
      default:
1790
19.6k
        assert(type == kNoCompression);  // Others should be excluded above
1791
19.6k
        return nullptr;
1792
0
      case kSnappyCompression:
1793
0
        return std::make_unique<BuiltinSnappyCompressorV2>(opts);
1794
0
      case kZlibCompression:
1795
0
        return std::make_unique<BuiltinZlibCompressorV2>(opts);
1796
0
      case kBZip2Compression:
1797
0
        return std::make_unique<BuiltinBZip2CompressorV2>(opts);
1798
0
      case kLZ4Compression:
1799
0
      case kLZ4HCCompression: {
1800
        // LZ4 and LZ4HC share the same wire format and decompressor. It is a
1801
        // historical oddity that they are distinct CompressionTypes. To blend
1802
        // them together more naturally, we now allow either configured
1803
        // compression type to access the same spectrum of compression levels
1804
        // supported by LZ4 and LZ4HC compressors. The appropriate compressor
1805
        // for the specified level is chosen regardless of which of the two
1806
        // types is configured, except that the default levels vary:
1807
        //   level <= 0    -> LZ4 fast (acceleration = max(1, -level))
1808
        //   level >= 1    -> LZ4HC (that level, 1..12)
1809
        //   default level ->
1810
        //     LZ4 -> LZ4 fast, acceleration 1 (equivalent to level -1)
1811
        //     LZ4HC -> LZ4HC level 9
1812
0
        bool use_hc;
1813
0
        if (opts.level == CompressionOptions::kDefaultCompressionLevel) {
1814
0
          use_hc = (type == kLZ4HCCompression);
1815
0
        } else {
1816
0
          use_hc = (opts.level >= 1);
1817
0
        }
1818
0
        if (use_hc) {
1819
0
          return std::make_unique<BuiltinLZ4HCCompressorV2>(opts);
1820
0
        } else {
1821
0
          return std::make_unique<BuiltinLZ4CompressorV2NoDict>(opts);
1822
0
        }
1823
0
      }
1824
0
      case kXpressCompression:
1825
0
        return std::make_unique<BuiltinXpressCompressorV2>(opts);
1826
0
      case kZSTD:
1827
0
        return std::make_unique<BuiltinZSTDCompressorV2>(opts);
1828
19.6k
    }
1829
19.6k
  }
1830
1831
0
  std::shared_ptr<Decompressor> GetDecompressor() override {
1832
0
    return GetGeneralDecompressor();
1833
0
  }
1834
1835
  std::shared_ptr<Decompressor> GetDecompressorOptimizeFor(
1836
0
      CompressionType optimize_for_type) override {
1837
0
    if (optimize_for_type == kZSTD) {
1838
0
      return GetZstdDecompressor();
1839
0
    } else {
1840
0
      return GetGeneralDecompressor();
1841
0
    }
1842
0
  }
1843
1844
  std::shared_ptr<Decompressor> GetDecompressorForTypes(
1845
      const CompressionType* types_begin,
1846
0
      const CompressionType* types_end) override {
1847
0
    if (types_begin == types_end) {
1848
0
      return nullptr;
1849
0
    } else if (types_begin + 1 == types_end &&
1850
0
               *types_begin == kSnappyCompression) {
1851
      // Exclusively Snappy
1852
0
      return GetSnappyDecompressor();
1853
0
    } else if (std::find(types_begin, types_end, kZSTD) != types_end) {
1854
      // Includes ZSTD
1855
0
      return GetZstdDecompressor();
1856
0
    } else {
1857
      // Everything else
1858
0
      return GetGeneralDecompressor();
1859
0
    }
1860
0
  }
1861
1862
19.6k
  bool SupportsCompressionType(CompressionType type) const override {
1863
19.6k
    return CompressionTypeSupported(type);
1864
19.6k
  }
1865
1866
 protected:
1867
  BuiltinDecompressorV2 decompressor_;
1868
  BuiltinDecompressorV2OptimizeZstd zstd_decompressor_;
1869
  BuiltinDecompressorV2SnappyOnly snappy_decompressor_;
1870
1871
 public:
1872
0
  inline std::shared_ptr<Decompressor> GetGeneralDecompressor() {
1873
0
    return std::shared_ptr<Decompressor>(shared_from_this(), &decompressor_);
1874
0
  }
1875
1876
0
  inline std::shared_ptr<Decompressor> GetZstdDecompressor() {
1877
0
    return std::shared_ptr<Decompressor>(shared_from_this(),
1878
0
                                         &zstd_decompressor_);
1879
0
  }
1880
1881
0
  inline std::shared_ptr<Decompressor> GetSnappyDecompressor() {
1882
0
    return std::shared_ptr<Decompressor>(shared_from_this(),
1883
0
                                         &snappy_decompressor_);
1884
0
  }
1885
};
1886
1887
const std::shared_ptr<BuiltinCompressionManagerV2>
1888
    kBuiltinCompressionManagerV2 =
1889
        std::make_shared<BuiltinCompressionManagerV2>();
1890
1891
std::shared_ptr<Decompressor>
1892
0
BuiltinZSTDCompressorV2::GetOptimizedDecompressor() const {
1893
0
  return kBuiltinCompressionManagerV2->GetZstdDecompressor();
1894
0
}
1895
1896
std::shared_ptr<Decompressor>
1897
0
BuiltinSnappyCompressorV2::GetOptimizedDecompressor() const {
1898
0
  return kBuiltinCompressionManagerV2->GetSnappyDecompressor();
1899
0
}
1900
1901
}  // namespace
1902
1903
Status CompressionManager::CreateFromString(
1904
    const ConfigOptions& config_options, const std::string& value,
1905
195k
    std::shared_ptr<CompressionManager>* result) {
1906
195k
  if (value == kNullptrString || value.empty()) {
1907
195k
    result->reset();
1908
195k
    return Status::OK();
1909
195k
  }
1910
1911
238
  static std::once_flag loaded;
1912
238
  std::call_once(loaded, [&]() {
1913
0
    auto& library = *ObjectLibrary::Default();
1914
    // TODO: try to enhance ObjectLibrary to support singletons
1915
0
    library.AddFactory<CompressionManager>(
1916
0
        kBuiltinCompressionManagerV2->CompatibilityName(),
1917
0
        [](const std::string& /*uri*/,
1918
0
           std::unique_ptr<CompressionManager>* guard,
1919
0
           std::string* /*errmsg*/) {
1920
0
          *guard = std::make_unique<BuiltinCompressionManagerV2>();
1921
0
          return guard->get();
1922
0
        });
1923
0
  });
1924
1925
238
  std::string id;
1926
238
  std::unordered_map<std::string, std::string> opt_map;
1927
238
  Status status = Customizable::GetOptionsMap(config_options, result->get(),
1928
238
                                              value, &id, &opt_map);
1929
238
  if (!status.ok()) {  // GetOptionsMap failed
1930
0
    return status;
1931
238
  } else if (id.empty()) {  // We have no Id but have options.  Not good
1932
0
    return Status::NotSupported("Cannot reset object ", id);
1933
238
  } else {
1934
238
    status = config_options.registry->NewSharedObject(id, result);
1935
238
  }
1936
238
  if (config_options.ignore_unsupported_options && status.IsNotSupported()) {
1937
0
    return Status::OK();
1938
238
  } else if (status.ok()) {
1939
0
    status = Customizable::ConfigureNewObject(config_options, result->get(),
1940
0
                                              opt_map);
1941
0
  }
1942
238
  return status;
1943
238
}
1944
1945
std::string CompressionManager::CompressionTypeToString(
1946
0
    CompressionType type) const {
1947
0
  return ROCKSDB_NAMESPACE::CompressionTypeToString(type);
1948
0
}
1949
1950
std::shared_ptr<CompressionManager>
1951
0
CompressionManager::FindCompatibleCompressionManager(Slice compatibility_name) {
1952
0
  if (compatibility_name.compare(CompatibilityName()) == 0) {
1953
0
    return shared_from_this();
1954
0
  } else {
1955
0
    std::shared_ptr<CompressionManager> out;
1956
0
    Status s =
1957
0
        CreateFromString(ConfigOptions(), compatibility_name.ToString(), &out);
1958
0
    if (s.ok()) {
1959
0
      return out;
1960
0
    } else {
1961
0
      return nullptr;
1962
0
    }
1963
0
  }
1964
0
}
1965
1966
19.6k
const std::shared_ptr<CompressionManager>& GetBuiltinV2CompressionManager() {
1967
19.6k
  static const std::shared_ptr<CompressionManager> v2_as_base =
1968
19.6k
      kBuiltinCompressionManagerV2;
1969
19.6k
  return v2_as_base;
1970
19.6k
}
1971
1972
// ***********************************************************************
1973
// END built-in implementation of customization interface
1974
// ***********************************************************************
1975
1976
Status LegacyForceBuiltinCompression(
1977
    Compressor& builtin_compressor,
1978
    Compressor::ManagedWorkingArea* working_area, Slice from,
1979
0
    GrowableBuffer* to) {
1980
  // For legacy cases that store compressed data even when it's larger than the
1981
  // uncompressed data (!!!), we need a reliable upper bound on the compressed
1982
  // size. This is based on consulting various algorithms documentation etc.
1983
  // and adding ~4 bytes for encoded uncompressed size. (Snappy is the worst
1984
  // case for multiplicative overhead at n + n/6, bounded by 19*n/16 to avoid
1985
  // costly division. Bzip2 is the worst case for additive overhead at 600
1986
  // bytes.)
1987
0
  size_t n = from.size();
1988
0
  size_t upper_bound = ((19 * n) >> 4) + 604;
1989
  // The upper bound has only been established considering built-in compression
1990
  // types through kZSTD. (Might need updating if this fails.)
1991
0
  assert(builtin_compressor.GetPreferredCompressionType() <= kZSTD);
1992
1993
0
  to->ResetForSize(upper_bound);
1994
0
  CompressionType actual_type = kNoCompression;
1995
0
  Status s = builtin_compressor.CompressBlock(
1996
0
      from, to->data(), &to->MutableSize(), &actual_type, working_area);
1997
0
  TEST_SYNC_POINT_CALLBACK("LegacyForceBuiltinCompression:TamperWithStatus",
1998
0
                           &s);
1999
2000
0
  if (!s.ok()) {
2001
0
    return s;
2002
0
  }
2003
0
  if (actual_type == kNoCompression) {
2004
    // abort in debug builds
2005
0
    assert(actual_type != kNoCompression);
2006
0
    return Status::Corruption("Compression unexpectedly declined or aborted");
2007
0
  }
2008
0
  assert(actual_type == builtin_compressor.GetPreferredCompressionType());
2009
0
  return Status::OK();
2010
0
}
2011
2012
}  // namespace ROCKSDB_NAMESPACE