mirror of
https://github.com/facebook/rocksdb.git
synced 2026-07-07 14:47:40 +08:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 626550343f | |||
| e12d15320a | |||
| 6a5e8486b6 | |||
| 940641c3cc | |||
| b6c49fc5b4 | |||
| a9ed4139fc | |||
| 5089e12116 | |||
| 7d7b4e04db | |||
| 9b450cbae6 | |||
| 32ccae27f4 | |||
| 0ac22f2921 | |||
| 6633bd0caa | |||
| 1e3fe96ffc | |||
| 4e69cf0d20 | |||
| 66d2c15e00 | |||
| e49a7097b4 | |||
| 60842997c5 | |||
| d2239f0fce | |||
| 6278420cd0 | |||
| abf9ad9193 | |||
| 457f746a78 |
+17
-1
@@ -1,5 +1,20 @@
|
||||
# Rocksdb Change Log
|
||||
## Unreleased
|
||||
|
||||
## 5.14.3 (8/21/2018)
|
||||
### Public API Change
|
||||
* The merge operands are passed to `MergeOperator::ShouldMerge` in the reversed order relative to how they were merged (passed to FullMerge or FullMergeV2) for performance reasons
|
||||
### Bug Fixes
|
||||
* Fixes DBImpl::FindObsoleteFiles() calling GetChildren() on the same path
|
||||
|
||||
## 5.14.2 (7/3/2018)
|
||||
### Bug Fixes
|
||||
* Change default value of `bytes_max_delete_chunk` to 0 in NewSstFileManager() as it doesn't work well with checkpoints.
|
||||
* Set DEBUG_LEVEL=0 for RocksJava Mac Release build.
|
||||
|
||||
## 5.14.1 (6/20/2018)
|
||||
### Bug Fixes
|
||||
* Fix block-based table reader pinning blocks throughout its lifetime, causing memory usage increase.
|
||||
* Fix bug with prefix search in partition filters where a shared prefix would be ignored from the later partitions. The bug could report an eixstent key as missing. The bug could be triggered if prefix_extractor is set and partition filters is enabled.
|
||||
|
||||
## 5.14.0 (5/16/2018)
|
||||
### Public API Change
|
||||
@@ -26,6 +41,7 @@
|
||||
* Fix `BackupableDBOptions::max_valid_backups_to_open` to not delete backup files when refcount cannot be accurately determined.
|
||||
* Fix memory leak when pin_l0_filter_and_index_blocks_in_cache is used with partitioned filters
|
||||
* Disable rollback of merge operands in WritePrepared transactions to work around an issue in MyRocks. It can be enabled back by setting TransactionDBOptions::rollback_merge_operands to true.
|
||||
* Fix bug with prefix search in partition filters where a shared prefix would be ignored from the later partitions. The bug could report an eixstent key as missing. The bug could be triggered if prefix_extractor is set and partition filters is enabled.
|
||||
|
||||
### Java API Changes
|
||||
* Add `BlockBasedTableConfig.setBlockCache` to allow sharing a block cache across DB instances.
|
||||
|
||||
@@ -85,6 +85,10 @@ ifeq ($(MAKECMDGOALS),rocksdbjavastaticrelease)
|
||||
DEBUG_LEVEL=0
|
||||
endif
|
||||
|
||||
ifeq ($(MAKECMDGOALS),rocksdbjavastaticreleasedocker)
|
||||
DEBUG_LEVEL=0
|
||||
endif
|
||||
|
||||
ifeq ($(MAKECMDGOALS),rocksdbjavastaticpublish)
|
||||
DEBUG_LEVEL=0
|
||||
endif
|
||||
|
||||
@@ -28,7 +28,7 @@ rocksdb_compiler_flags = [
|
||||
# Added missing flags from output of build_detect_platform
|
||||
"-DROCKSDB_PTHREAD_ADAPTIVE_MUTEX",
|
||||
"-DROCKSDB_BACKTRACE",
|
||||
"-Wshorten-64-to-32",
|
||||
"-Wnarrowing",
|
||||
]
|
||||
|
||||
rocksdb_external_deps = [
|
||||
@@ -556,13 +556,13 @@ ROCKS_TESTS = [
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
"db_iter_test",
|
||||
"db/db_iter_test.cc",
|
||||
"db_iter_stress_test",
|
||||
"db/db_iter_stress_test.cc",
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
"db_iter_stress_test",
|
||||
"db/db_iter_stress_test.cc",
|
||||
"db_iter_test",
|
||||
"db/db_iter_test.cc",
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
@@ -1069,14 +1069,3 @@ if not is_opt_mode:
|
||||
command = [TEST_RUNNER, BUCK_BINS + test_bin]
|
||||
)
|
||||
|
||||
custom_unittest(
|
||||
name = "make_rocksdbjavastatic",
|
||||
command = ["internal_repo_rocksdb/make_rocksdbjavastatic.sh"],
|
||||
type = "simple",
|
||||
)
|
||||
|
||||
custom_unittest(
|
||||
name = "make_rocksdb_lite_release",
|
||||
command = ["internal_repo_rocksdb/make_rocksdb_lite_release.sh"],
|
||||
type = "simple",
|
||||
)
|
||||
|
||||
@@ -32,7 +32,7 @@ rocksdb_compiler_flags = [
|
||||
# Added missing flags from output of build_detect_platform
|
||||
"-DROCKSDB_PTHREAD_ADAPTIVE_MUTEX",
|
||||
"-DROCKSDB_BACKTRACE",
|
||||
"-Wshorten-64-to-32",
|
||||
"-Wnarrowing",
|
||||
]
|
||||
|
||||
rocksdb_external_deps = [
|
||||
@@ -135,15 +135,4 @@ if not is_opt_mode:
|
||||
command = [TEST_RUNNER, BUCK_BINS + test_bin]
|
||||
)
|
||||
|
||||
custom_unittest(
|
||||
name = "make_rocksdbjavastatic",
|
||||
command = ["internal_repo_rocksdb/make_rocksdbjavastatic.sh"],
|
||||
type = "simple",
|
||||
)
|
||||
|
||||
custom_unittest(
|
||||
name = "make_rocksdb_lite_release",
|
||||
command = ["internal_repo_rocksdb/make_rocksdb_lite_release.sh"],
|
||||
type = "simple",
|
||||
)
|
||||
"""
|
||||
|
||||
@@ -2824,6 +2824,27 @@ TEST_F(ColumnFamilyTest, CreateAndDestoryOptions) {
|
||||
ASSERT_OK(db_->DestroyColumnFamilyHandle(cfh));
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyTest, CreateDropAndDestroy) {
|
||||
ColumnFamilyHandle* cfh;
|
||||
Open();
|
||||
ASSERT_OK(db_->CreateColumnFamily(ColumnFamilyOptions(), "yoyo", &cfh));
|
||||
ASSERT_OK(db_->Put(WriteOptions(), cfh, "foo", "bar"));
|
||||
ASSERT_OK(db_->Flush(FlushOptions(), cfh));
|
||||
ASSERT_OK(db_->DropColumnFamily(cfh));
|
||||
ASSERT_OK(db_->DestroyColumnFamilyHandle(cfh));
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyTest, CreateDropAndDestroyWithoutFileDeletion) {
|
||||
ColumnFamilyHandle* cfh;
|
||||
Open();
|
||||
ASSERT_OK(db_->CreateColumnFamily(ColumnFamilyOptions(), "yoyo", &cfh));
|
||||
ASSERT_OK(db_->Put(WriteOptions(), cfh, "foo", "bar"));
|
||||
ASSERT_OK(db_->Flush(FlushOptions(), cfh));
|
||||
ASSERT_OK(db_->DisableFileDeletions());
|
||||
ASSERT_OK(db_->DropColumnFamily(cfh));
|
||||
ASSERT_OK(db_->DestroyColumnFamilyHandle(cfh));
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(ColumnFamilyTest, FlushCloseWALFiles) {
|
||||
SpecialEnv env(Env::Default());
|
||||
|
||||
+8
-3
@@ -12,6 +12,7 @@
|
||||
#define __STDC_FORMAT_MACROS
|
||||
#endif
|
||||
#include <inttypes.h>
|
||||
#include <set>
|
||||
#include <unordered_set>
|
||||
#include "db/event_helpers.h"
|
||||
#include "db/memtable_list.h"
|
||||
@@ -99,10 +100,10 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
if (doing_the_full_scan) {
|
||||
InfoLogPrefix info_log_prefix(!immutable_db_options_.db_log_dir.empty(),
|
||||
dbname_);
|
||||
std::vector<std::string> paths;
|
||||
std::set<std::string> paths;
|
||||
for (size_t path_id = 0; path_id < immutable_db_options_.db_paths.size();
|
||||
path_id++) {
|
||||
paths.emplace_back(immutable_db_options_.db_paths[path_id].path);
|
||||
paths.insert(immutable_db_options_.db_paths[path_id].path);
|
||||
}
|
||||
|
||||
// Note that if cf_paths is not specified in the ColumnFamilyOptions
|
||||
@@ -113,7 +114,11 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
for (size_t path_id = 0; path_id < cfd->ioptions()->cf_paths.size();
|
||||
path_id++) {
|
||||
paths.emplace_back(cfd->ioptions()->cf_paths[path_id].path);
|
||||
auto& path = cfd->ioptions()->cf_paths[path_id].path;
|
||||
|
||||
if (paths.find(path) == paths.end()) {
|
||||
paths.insert(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-4
@@ -36,7 +36,9 @@ class TestIterator : public InternalIterator {
|
||||
valid_(false),
|
||||
sequence_number_(0),
|
||||
iter_(0),
|
||||
cmp(comparator) {}
|
||||
cmp(comparator) {
|
||||
data_.reserve(16);
|
||||
}
|
||||
|
||||
void AddPut(std::string argkey, std::string argvalue) {
|
||||
Add(argkey, kTypeValue, argvalue);
|
||||
@@ -2605,7 +2607,7 @@ TEST_F(DBIterWithMergeIterTest, InnerMergeIteratorDataRace1) {
|
||||
// MergeIterator::Prev() realized the mem table iterator is at its end
|
||||
// and before an SeekToLast() is called.
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"MergeIterator::Prev:BeforeSeekToLast",
|
||||
"MergeIterator::Prev:BeforePrev",
|
||||
[&](void* /*arg*/) { internal_iter2_->Add("z", kTypeValue, "7", 12u); });
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
@@ -2641,7 +2643,7 @@ TEST_F(DBIterWithMergeIterTest, InnerMergeIteratorDataRace2) {
|
||||
// mem table after MergeIterator::Prev() realized the mem tableiterator is at
|
||||
// its end and before an SeekToLast() is called.
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"MergeIterator::Prev:BeforeSeekToLast", [&](void* /*arg*/) {
|
||||
"MergeIterator::Prev:BeforePrev", [&](void* /*arg*/) {
|
||||
internal_iter2_->Add("z", kTypeValue, "7", 12u);
|
||||
internal_iter2_->Add("z", kTypeValue, "7", 11u);
|
||||
});
|
||||
@@ -2679,7 +2681,7 @@ TEST_F(DBIterWithMergeIterTest, InnerMergeIteratorDataRace3) {
|
||||
// mem table after MergeIterator::Prev() realized the mem table iterator is at
|
||||
// its end and before an SeekToLast() is called.
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"MergeIterator::Prev:BeforeSeekToLast", [&](void* /*arg*/) {
|
||||
"MergeIterator::Prev:BeforePrev", [&](void* /*arg*/) {
|
||||
internal_iter2_->Add("z", kTypeValue, "7", 16u, true);
|
||||
internal_iter2_->Add("z", kTypeValue, "7", 15u, true);
|
||||
internal_iter2_->Add("z", kTypeValue, "7", 14u, true);
|
||||
|
||||
@@ -2043,6 +2043,43 @@ TEST_P(DBIteratorTest, CreationFailure) {
|
||||
delete iter;
|
||||
}
|
||||
|
||||
TEST_P(DBIteratorTest, UpperBoundWithChangeDirection) {
|
||||
Options options = CurrentOptions();
|
||||
options.max_sequential_skip_in_iterations = 3;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// write a bunch of kvs to the database.
|
||||
ASSERT_OK(Put("a", "1"));
|
||||
ASSERT_OK(Put("y", "1"));
|
||||
ASSERT_OK(Put("y1", "1"));
|
||||
ASSERT_OK(Put("y2", "1"));
|
||||
ASSERT_OK(Put("y3", "1"));
|
||||
ASSERT_OK(Put("z", "1"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("a", "1"));
|
||||
ASSERT_OK(Put("z", "1"));
|
||||
ASSERT_OK(Put("bar", "1"));
|
||||
ASSERT_OK(Put("foo", "1"));
|
||||
|
||||
std::string upper_bound = "x";
|
||||
Slice ub_slice(upper_bound);
|
||||
ReadOptions ro;
|
||||
ro.iterate_upper_bound = &ub_slice;
|
||||
ro.max_skippable_internal_keys = 1000;
|
||||
|
||||
Iterator* iter = NewIterator(ro);
|
||||
iter->Seek("foo");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("foo", iter->key().ToString());
|
||||
|
||||
iter->Prev();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ("bar", iter->key().ToString());
|
||||
|
||||
delete iter;
|
||||
}
|
||||
|
||||
TEST_P(DBIteratorTest, TableFilter) {
|
||||
ASSERT_OK(Put("a", "1"));
|
||||
dbfull()->Flush(FlushOptions());
|
||||
@@ -2109,6 +2146,47 @@ TEST_P(DBIteratorTest, TableFilter) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(DBIteratorTest, UpperBoundWithPrevReseek) {
|
||||
Options options = CurrentOptions();
|
||||
options.max_sequential_skip_in_iterations = 3;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// write a bunch of kvs to the database.
|
||||
ASSERT_OK(Put("a", "1"));
|
||||
ASSERT_OK(Put("y", "1"));
|
||||
ASSERT_OK(Put("z", "1"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("a", "1"));
|
||||
ASSERT_OK(Put("z", "1"));
|
||||
ASSERT_OK(Put("bar", "1"));
|
||||
ASSERT_OK(Put("foo", "1"));
|
||||
ASSERT_OK(Put("foo", "2"));
|
||||
|
||||
ASSERT_OK(Put("foo", "3"));
|
||||
ASSERT_OK(Put("foo", "4"));
|
||||
ASSERT_OK(Put("foo", "5"));
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
ASSERT_OK(Put("foo", "6"));
|
||||
|
||||
std::string upper_bound = "x";
|
||||
Slice ub_slice(upper_bound);
|
||||
ReadOptions ro;
|
||||
ro.snapshot = snapshot;
|
||||
ro.iterate_upper_bound = &ub_slice;
|
||||
|
||||
Iterator* iter = NewIterator(ro);
|
||||
iter->SeekForPrev("goo");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("foo", iter->key().ToString());
|
||||
iter->Prev();
|
||||
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("bar", iter->key().ToString());
|
||||
|
||||
delete iter;
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
|
||||
TEST_P(DBIteratorTest, SkipStatistics) {
|
||||
Options options = CurrentOptions();
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
|
||||
+1
-1
@@ -696,7 +696,7 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||
*(s->merge_in_progress) = true;
|
||||
merge_context->PushOperand(
|
||||
v, s->inplace_update_support == false /* operand_pinned */);
|
||||
if (merge_operator->ShouldMerge(merge_context->GetOperands())) {
|
||||
if (merge_operator->ShouldMerge(merge_context->GetOperandsDirectionBackward())) {
|
||||
*(s->status) = MergeHelper::TimedFullMerge(
|
||||
merge_operator, s->key->user_key(), nullptr,
|
||||
merge_context->GetOperands(), s->value, s->logger, s->statistics,
|
||||
|
||||
+16
-1
@@ -74,8 +74,13 @@ class MergeContext {
|
||||
return (*operand_list_)[index];
|
||||
}
|
||||
|
||||
// Return all the operands.
|
||||
// Same as GetOperandsDirectionForward
|
||||
const std::vector<Slice>& GetOperands() {
|
||||
return GetOperandsDirectionForward();
|
||||
}
|
||||
|
||||
// Return all the operands in the order as they were merged (passed to FullMerge or FullMergeV2)
|
||||
const std::vector<Slice>& GetOperandsDirectionForward() {
|
||||
if (!operand_list_) {
|
||||
return empty_operand_list;
|
||||
}
|
||||
@@ -84,6 +89,16 @@ class MergeContext {
|
||||
return *operand_list_;
|
||||
}
|
||||
|
||||
// Return all the operands in the reversed order relative to how they were merged (passed to FullMerge or FullMergeV2)
|
||||
const std::vector<Slice>& GetOperandsDirectionBackward() {
|
||||
if (!operand_list_) {
|
||||
return empty_operand_list;
|
||||
}
|
||||
|
||||
SetDirectionBackward();
|
||||
return *operand_list_;
|
||||
}
|
||||
|
||||
private:
|
||||
void Initialize() {
|
||||
if (!operand_list_) {
|
||||
|
||||
@@ -195,6 +195,11 @@ class MergeOperator {
|
||||
// during a point lookup, thereby helping in limiting the number of levels to
|
||||
// read from.
|
||||
// Doesn't help with iterators.
|
||||
//
|
||||
// Note: the merge operands are passed to this function in the reversed order
|
||||
// relative to how they were merged (passed to FullMerge or FullMergeV2)
|
||||
// for performance reasons, see also:
|
||||
// https://github.com/facebook/rocksdb/issues/3865
|
||||
virtual bool ShouldMerge(const std::vector<Slice>& /*operands*/) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -98,12 +98,13 @@ class SstFileManager {
|
||||
// DeleteScheduler immediately
|
||||
// @param bytes_max_delete_chunk: if a single file is larger than delete chunk,
|
||||
// ftruncate the file by this size each time, rather than dropping the whole
|
||||
// file. 0 means to always delete the whole file.
|
||||
// file. 0 means to always delete the whole file. NOTE this options may not
|
||||
// work well with checkpoints, which relies on file system hard links.
|
||||
extern SstFileManager* NewSstFileManager(
|
||||
Env* env, std::shared_ptr<Logger> info_log = nullptr,
|
||||
std::string trash_dir = "", int64_t rate_bytes_per_sec = 0,
|
||||
bool delete_existing_trash = true, Status* status = nullptr,
|
||||
double max_trash_db_ratio = 0.25,
|
||||
uint64_t bytes_max_delete_chunk = 64 * 1024 * 1024);
|
||||
uint64_t bytes_max_delete_chunk = 0);
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -210,12 +210,20 @@ class LDBCommand {
|
||||
bool ParseStringOption(const std::map<std::string, std::string>& options,
|
||||
const std::string& option, std::string* value);
|
||||
|
||||
/**
|
||||
* Returns the value of the specified option as a boolean.
|
||||
* default_val is used if the option is not found in options.
|
||||
* Throws an exception if the value of the option is not
|
||||
* "true" or "false" (case insensitive).
|
||||
*/
|
||||
bool ParseBooleanOption(const std::map<std::string, std::string>& options,
|
||||
const std::string& option, bool default_val);
|
||||
|
||||
Options options_;
|
||||
std::vector<ColumnFamilyDescriptor> column_families_;
|
||||
LDBOptions ldb_options_;
|
||||
|
||||
private:
|
||||
friend class WALDumperCommand;
|
||||
/**
|
||||
* Interpret command line options and flags to determine if the key
|
||||
* should be input/output in hex.
|
||||
@@ -230,15 +238,6 @@ class LDBCommand {
|
||||
bool IsValueHex(const std::map<std::string, std::string>& options,
|
||||
const std::vector<std::string>& flags);
|
||||
|
||||
/**
|
||||
* Returns the value of the specified option as a boolean.
|
||||
* default_val is used if the option is not found in options.
|
||||
* Throws an exception if the value of the option is not
|
||||
* "true" or "false" (case insensitive).
|
||||
*/
|
||||
bool ParseBooleanOption(const std::map<std::string, std::string>& options,
|
||||
const std::string& option, bool default_val);
|
||||
|
||||
/**
|
||||
* Converts val to a boolean.
|
||||
* val must be either true or false (case insensitive).
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
#define ROCKSDB_MAJOR 5
|
||||
#define ROCKSDB_MINOR 14
|
||||
#define ROCKSDB_PATCH 0
|
||||
#define ROCKSDB_PATCH 3
|
||||
|
||||
// Do not use these. We made the mistake of declaring macros starting with
|
||||
// double underscore. Now we have to live with our choice. We'll deprecate these
|
||||
|
||||
@@ -3230,7 +3230,7 @@ Java_org_rocksdb_ColumnFamilyOptions_setCompactionFilterFactoryHandle(
|
||||
JNIEnv* /*env*/, jobject /*jobj*/, jlong jopt_handle,
|
||||
jlong jcompactionfilterfactory_handle) {
|
||||
auto* cff_factory =
|
||||
reinterpret_cast<std::shared_ptr<rocksdb::CompactionFilterFactory>*>(
|
||||
reinterpret_cast<std::shared_ptr<rocksdb::CompactionFilterFactoryJniCallback>*>(
|
||||
jcompactionfilterfactory_handle);
|
||||
reinterpret_cast<rocksdb::ColumnFamilyOptions*>(jopt_handle)
|
||||
->compaction_filter_factory = *cff_factory;
|
||||
|
||||
+49
-34
@@ -4292,25 +4292,12 @@ class JniUtil {
|
||||
* @param bytes The bytes to copy
|
||||
*
|
||||
* @return the Java byte[] or nullptr if an exception occurs
|
||||
*
|
||||
* @throws RocksDBException thrown
|
||||
* if memory size to copy exceeds general java specific array size limitation.
|
||||
*/
|
||||
static jbyteArray copyBytes(JNIEnv* env, std::string bytes) {
|
||||
const jsize jlen = static_cast<jsize>(bytes.size());
|
||||
|
||||
jbyteArray jbytes = env->NewByteArray(jlen);
|
||||
if(jbytes == nullptr) {
|
||||
// exception thrown: OutOfMemoryError
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
env->SetByteArrayRegion(jbytes, 0, jlen,
|
||||
const_cast<jbyte*>(reinterpret_cast<const jbyte*>(bytes.c_str())));
|
||||
if(env->ExceptionCheck()) {
|
||||
// exception thrown: ArrayIndexOutOfBoundsException
|
||||
env->DeleteLocalRef(jbytes);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return jbytes;
|
||||
return createJavaByteArrayWithSizeCheck(env, bytes.c_str(), bytes.size());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -4473,6 +4460,47 @@ class JniUtil {
|
||||
|
||||
return jbyte_strings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies bytes to a new jByteArray with the check of java array size limitation.
|
||||
*
|
||||
* @param bytes pointer to memory to copy to a new jByteArray
|
||||
* @param size number of bytes to copy
|
||||
*
|
||||
* @return the Java byte[] or nullptr if an exception occurs
|
||||
*
|
||||
* @throws RocksDBException thrown
|
||||
* if memory size to copy exceeds general java array size limitation to avoid overflow.
|
||||
*/
|
||||
static jbyteArray createJavaByteArrayWithSizeCheck(JNIEnv* env, const char* bytes, const size_t size) {
|
||||
// Limitation for java array size is vm specific
|
||||
// In general it cannot exceed Integer.MAX_VALUE (2^31 - 1)
|
||||
// Current HotSpot VM limitation for array size is Integer.MAX_VALUE - 5 (2^31 - 1 - 5)
|
||||
// It means that the next call to env->NewByteArray can still end with
|
||||
// OutOfMemoryError("Requested array size exceeds VM limit") coming from VM
|
||||
static const size_t MAX_JARRAY_SIZE = (static_cast<size_t>(1)) << 31;
|
||||
if(size > MAX_JARRAY_SIZE) {
|
||||
rocksdb::RocksDBExceptionJni::ThrowNew(env, "Requested array size exceeds VM limit");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const jsize jlen = static_cast<jsize>(size);
|
||||
jbyteArray jbytes = env->NewByteArray(jlen);
|
||||
if(jbytes == nullptr) {
|
||||
// exception thrown: OutOfMemoryError
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
env->SetByteArrayRegion(jbytes, 0, jlen,
|
||||
const_cast<jbyte*>(reinterpret_cast<const jbyte*>(bytes)));
|
||||
if(env->ExceptionCheck()) {
|
||||
// exception thrown: ArrayIndexOutOfBoundsException
|
||||
env->DeleteLocalRef(jbytes);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return jbytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies bytes from a rocksdb::Slice to a jByteArray
|
||||
@@ -4481,25 +4509,12 @@ class JniUtil {
|
||||
* @param bytes The bytes to copy
|
||||
*
|
||||
* @return the Java byte[] or nullptr if an exception occurs
|
||||
*
|
||||
* @throws RocksDBException thrown
|
||||
* if memory size to copy exceeds general java specific array size limitation.
|
||||
*/
|
||||
static jbyteArray copyBytes(JNIEnv* env, const Slice& bytes) {
|
||||
const jsize jlen = static_cast<jsize>(bytes.size());
|
||||
|
||||
jbyteArray jbytes = env->NewByteArray(jlen);
|
||||
if(jbytes == nullptr) {
|
||||
// exception thrown: OutOfMemoryError
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
env->SetByteArrayRegion(jbytes, 0, jlen,
|
||||
const_cast<jbyte*>(reinterpret_cast<const jbyte*>(bytes.data())));
|
||||
if(env->ExceptionCheck()) {
|
||||
// exception thrown: ArrayIndexOutOfBoundsException
|
||||
env->DeleteLocalRef(jbytes);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return jbytes;
|
||||
return createJavaByteArrayWithSizeCheck(env, bytes.data(), bytes.size());
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -138,7 +138,7 @@ jobject Java_org_rocksdb_SstFileManager_getTrackedFiles(JNIEnv* env,
|
||||
|
||||
const rocksdb::HashMapJni::FnMapKV<const std::string, const uint64_t>
|
||||
fn_map_kv =
|
||||
[env, &tracked_files](
|
||||
[env](
|
||||
const std::pair<const std::string, const uint64_t>& pair) {
|
||||
const jstring jtracked_file_path =
|
||||
env->NewStringUTF(pair.first.c_str());
|
||||
|
||||
@@ -48,7 +48,7 @@ void Java_org_rocksdb_Transaction_setSnapshotOnNextOperation__JJ(
|
||||
jlong jtxn_notifier_handle) {
|
||||
auto* txn = reinterpret_cast<rocksdb::Transaction*>(jhandle);
|
||||
auto* txn_notifier =
|
||||
reinterpret_cast<std::shared_ptr<rocksdb::TransactionNotifier>*>(
|
||||
reinterpret_cast<std::shared_ptr<rocksdb::TransactionNotifierJniCallback>*>(
|
||||
jtxn_notifier_handle);
|
||||
txn->SetSnapshotOnNextOperation(*txn_notifier);
|
||||
}
|
||||
|
||||
@@ -309,7 +309,7 @@ jobject Java_org_rocksdb_TransactionDB_getLockStatusData(JNIEnv* env,
|
||||
|
||||
const rocksdb::HashMapJni::FnMapKV<const int32_t, const rocksdb::KeyLockInfo>
|
||||
fn_map_kv =
|
||||
[env, txn_db, &lock_status_data](
|
||||
[env](
|
||||
const std::pair<const int32_t, const rocksdb::KeyLockInfo>&
|
||||
pair) {
|
||||
const jobject jlong_column_family_id =
|
||||
|
||||
@@ -28,8 +28,10 @@ public class ColumnFamilyHandle extends RocksObject {
|
||||
* Gets the name of the Column Family.
|
||||
*
|
||||
* @return The name of the Column Family.
|
||||
*
|
||||
* @throws RocksDBException if an error occurs whilst retrieving the name.
|
||||
*/
|
||||
public byte[] getName() {
|
||||
public byte[] getName() throws RocksDBException {
|
||||
return getName(nativeHandle_);
|
||||
}
|
||||
|
||||
@@ -71,14 +73,22 @@ public class ColumnFamilyHandle extends RocksObject {
|
||||
}
|
||||
|
||||
final ColumnFamilyHandle that = (ColumnFamilyHandle) o;
|
||||
return rocksDB_.nativeHandle_ == that.rocksDB_.nativeHandle_ &&
|
||||
getID() == that.getID() &&
|
||||
Arrays.equals(getName(), that.getName());
|
||||
try {
|
||||
return rocksDB_.nativeHandle_ == that.rocksDB_.nativeHandle_ &&
|
||||
getID() == that.getID() &&
|
||||
Arrays.equals(getName(), that.getName());
|
||||
} catch (RocksDBException e) {
|
||||
throw new RuntimeException("Cannot compare column family handles", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(getName(), getID(), rocksDB_.nativeHandle_);
|
||||
try {
|
||||
return Objects.hash(getName(), getID(), rocksDB_.nativeHandle_);
|
||||
} catch (RocksDBException e) {
|
||||
throw new RuntimeException("Cannot calculate hash code of column family handle", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,7 +106,7 @@ public class ColumnFamilyHandle extends RocksObject {
|
||||
}
|
||||
}
|
||||
|
||||
private native byte[] getName(final long handle);
|
||||
private native byte[] getName(final long handle) throws RocksDBException;
|
||||
private native int getID(final long handle);
|
||||
private native ColumnFamilyDescriptor getDescriptor(final long handle) throws RocksDBException;
|
||||
@Override protected final native void disposeInternal(final long handle);
|
||||
|
||||
@@ -65,8 +65,11 @@ public class WriteBatch extends AbstractWriteBatch {
|
||||
* Retrieve the serialized version of this batch.
|
||||
*
|
||||
* @return the serialized representation of this write batch.
|
||||
*
|
||||
* @throws RocksDBException if an error occurs whilst retrieving
|
||||
* the serialized batch data.
|
||||
*/
|
||||
public byte[] data() {
|
||||
public byte[] data() throws RocksDBException {
|
||||
return data(nativeHandle_);
|
||||
}
|
||||
|
||||
@@ -253,7 +256,7 @@ public class WriteBatch extends AbstractWriteBatch {
|
||||
final int serializedLength);
|
||||
private native void iterate(final long handle, final long handlerHandle)
|
||||
throws RocksDBException;
|
||||
private native byte[] data(final long nativeHandle);
|
||||
private native byte[] data(final long nativeHandle) throws RocksDBException;
|
||||
private native long getDataSize(final long nativeHandle);
|
||||
private native boolean hasPut(final long nativeHandle);
|
||||
private native boolean hasDelete(final long nativeHandle);
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
package org.rocksdb;
|
||||
|
||||
import org.junit.Assume;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.util.*;
|
||||
@@ -143,6 +145,39 @@ public class RocksDBTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void getOutOfArrayMaxSizeValue() throws RocksDBException {
|
||||
final int numberOfValueSplits = 10;
|
||||
final int splitSize = Integer.MAX_VALUE / numberOfValueSplits;
|
||||
|
||||
Runtime runtime = Runtime.getRuntime();
|
||||
long neededMemory = ((long)(splitSize)) * (((long)numberOfValueSplits) + 3);
|
||||
boolean isEnoughMemory = runtime.maxMemory() - runtime.totalMemory() > neededMemory;
|
||||
Assume.assumeTrue(isEnoughMemory);
|
||||
|
||||
final byte[] valueSplit = new byte[splitSize];
|
||||
final byte[] key = "key".getBytes();
|
||||
|
||||
thrown.expect(RocksDBException.class);
|
||||
thrown.expectMessage("Requested array size exceeds VM limit");
|
||||
|
||||
// merge (numberOfValueSplits + 1) valueSplit's to get value size exceeding Integer.MAX_VALUE
|
||||
try (final StringAppendOperator stringAppendOperator = new StringAppendOperator();
|
||||
final Options opt = new Options()
|
||||
.setCreateIfMissing(true)
|
||||
.setMergeOperator(stringAppendOperator);
|
||||
final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) {
|
||||
db.put(key, valueSplit);
|
||||
for (int i = 0; i < numberOfValueSplits; i++) {
|
||||
db.merge(key, valueSplit);
|
||||
}
|
||||
db.get(key);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multiGet() throws RocksDBException, InterruptedException {
|
||||
try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath());
|
||||
|
||||
+4
-1
@@ -242,7 +242,7 @@ class BlockIter final : public InternalIterator {
|
||||
}
|
||||
|
||||
// Makes Valid() return false, status() return `s`, and Seek()/Prev()/etc do
|
||||
// nothing.
|
||||
// nothing. Calls cleanup functions.
|
||||
void Invalidate(Status s) {
|
||||
// Assert that the BlockIter is never deleted while Pinning is Enabled.
|
||||
assert(!pinned_iters_mgr_ ||
|
||||
@@ -252,6 +252,9 @@ class BlockIter final : public InternalIterator {
|
||||
current_ = restarts_;
|
||||
status_ = s;
|
||||
|
||||
// Call cleanup callbacks.
|
||||
Cleanable::Reset();
|
||||
|
||||
// Clear prev entries cache.
|
||||
prev_entries_keys_buff_.clear();
|
||||
prev_entries_.clear();
|
||||
|
||||
@@ -420,7 +420,7 @@ struct BlockBasedTable::Rep {
|
||||
|
||||
const ImmutableCFOptions& ioptions;
|
||||
const EnvOptions& env_options;
|
||||
const BlockBasedTableOptions& table_options;
|
||||
const BlockBasedTableOptions table_options;
|
||||
const FilterPolicy* const filter_policy;
|
||||
const InternalKeyComparator& internal_comparator;
|
||||
Status status;
|
||||
|
||||
@@ -72,8 +72,14 @@ inline void FullFilterBlockBuilder::AddPrefix(const Slice& key) {
|
||||
}
|
||||
}
|
||||
|
||||
void FullFilterBlockBuilder::Reset() {
|
||||
last_whole_key_recorded_ = false;
|
||||
last_prefix_recorded_ = false;
|
||||
}
|
||||
|
||||
Slice FullFilterBlockBuilder::Finish(const BlockHandle& /*tmp*/,
|
||||
Status* status) {
|
||||
Reset();
|
||||
// In this impl we ignore BlockHandle
|
||||
*status = Status::OK();
|
||||
if (num_added_ != 0) {
|
||||
|
||||
@@ -52,6 +52,7 @@ class FullFilterBlockBuilder : public FilterBlockBuilder {
|
||||
protected:
|
||||
virtual void AddKey(const Slice& key);
|
||||
std::unique_ptr<FilterBitsBuilder> filter_bits_builder_;
|
||||
virtual void Reset();
|
||||
|
||||
private:
|
||||
// important: all of these might point to invalid addresses
|
||||
|
||||
@@ -200,7 +200,7 @@ bool GetContext::SaveValue(const ParsedInternalKey& parsed_key,
|
||||
merge_context_->PushOperand(value, false);
|
||||
}
|
||||
if (merge_operator_ != nullptr &&
|
||||
merge_operator_->ShouldMerge(merge_context_->GetOperands())) {
|
||||
merge_operator_->ShouldMerge(merge_context_->GetOperandsDirectionBackward())) {
|
||||
state_ = kFound;
|
||||
if (LIKELY(pinnable_val_ != nullptr)) {
|
||||
Status merge_status = MergeHelper::TimedFullMerge(
|
||||
|
||||
@@ -228,28 +228,12 @@ class MergingIterator : public InternalIterator {
|
||||
Slice target = key();
|
||||
for (auto& child : children_) {
|
||||
if (&child != current_) {
|
||||
if (!prefix_seek_mode_) {
|
||||
child.Seek(target);
|
||||
if (child.Valid()) {
|
||||
// Child is at first entry >= key(). Step back one to be < key()
|
||||
TEST_SYNC_POINT_CALLBACK("MergeIterator::Prev:BeforePrev",
|
||||
&child);
|
||||
assert(child.status().ok());
|
||||
child.Prev();
|
||||
} else {
|
||||
// Child has no entries >= key(). Position at last entry.
|
||||
TEST_SYNC_POINT("MergeIterator::Prev:BeforeSeekToLast");
|
||||
considerStatus(child.status());
|
||||
child.SeekToLast();
|
||||
}
|
||||
child.SeekForPrev(target);
|
||||
TEST_SYNC_POINT_CALLBACK("MergeIterator::Prev:BeforePrev", &child);
|
||||
considerStatus(child.status());
|
||||
if (child.Valid() && comparator_->Equal(target, child.key())) {
|
||||
child.Prev();
|
||||
considerStatus(child.status());
|
||||
} else {
|
||||
child.SeekForPrev(target);
|
||||
considerStatus(child.status());
|
||||
if (child.Valid() && comparator_->Equal(target, child.key())) {
|
||||
child.Prev();
|
||||
considerStatus(child.status());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (child.Valid()) {
|
||||
|
||||
@@ -48,6 +48,7 @@ void PartitionedFilterBlockBuilder::MaybeCutAFilterBlock() {
|
||||
std::string& index_key = p_index_builder_->GetPartitionKey();
|
||||
filters.push_back({index_key, filter});
|
||||
filters_in_partition_ = 0;
|
||||
Reset();
|
||||
}
|
||||
|
||||
void PartitionedFilterBlockBuilder::AddKey(const Slice& key) {
|
||||
|
||||
@@ -242,6 +242,14 @@ LDBCommand* LDBCommand::SelectCommand(const ParsedParams& parsed_params) {
|
||||
} else if (parsed_params.cmd == RestoreCommand::Name()) {
|
||||
return new RestoreCommand(parsed_params.cmd_params,
|
||||
parsed_params.option_map, parsed_params.flags);
|
||||
} else if (parsed_params.cmd == WriteExternalSstFilesCommand::Name()) {
|
||||
return new WriteExternalSstFilesCommand(parsed_params.cmd_params,
|
||||
parsed_params.option_map,
|
||||
parsed_params.flags);
|
||||
} else if (parsed_params.cmd == IngestExternalSstFilesCommand::Name()) {
|
||||
return new IngestExternalSstFilesCommand(parsed_params.cmd_params,
|
||||
parsed_params.option_map,
|
||||
parsed_params.flags);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -2936,5 +2944,180 @@ void DBFileDumperCommand::DoCommand() {
|
||||
}
|
||||
}
|
||||
|
||||
void WriteExternalSstFilesCommand::Help(std::string& ret) {
|
||||
ret.append(" ");
|
||||
ret.append(WriteExternalSstFilesCommand::Name());
|
||||
ret.append(" <output_sst_path>");
|
||||
ret.append("\n");
|
||||
}
|
||||
|
||||
WriteExternalSstFilesCommand::WriteExternalSstFilesCommand(
|
||||
const std::vector<std::string>& params,
|
||||
const std::map<std::string, std::string>& options,
|
||||
const std::vector<std::string>& flags)
|
||||
: LDBCommand(
|
||||
options, flags, false /* is_read_only */,
|
||||
BuildCmdLineOptions({ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX, ARG_FROM,
|
||||
ARG_TO, ARG_CREATE_IF_MISSING})) {
|
||||
create_if_missing_ =
|
||||
IsFlagPresent(flags, ARG_CREATE_IF_MISSING) ||
|
||||
ParseBooleanOption(options, ARG_CREATE_IF_MISSING, false);
|
||||
if (params.size() != 1) {
|
||||
exec_state_ = LDBCommandExecuteResult::Failed(
|
||||
"output SST file path must be specified");
|
||||
} else {
|
||||
output_sst_path_ = params.at(0);
|
||||
}
|
||||
}
|
||||
|
||||
void WriteExternalSstFilesCommand::DoCommand() {
|
||||
if (!db_) {
|
||||
assert(GetExecuteState().IsFailed());
|
||||
return;
|
||||
}
|
||||
ColumnFamilyHandle* cfh = GetCfHandle();
|
||||
SstFileWriter sst_file_writer(EnvOptions(), db_->GetOptions(), cfh);
|
||||
Status status = sst_file_writer.Open(output_sst_path_);
|
||||
if (!status.ok()) {
|
||||
exec_state_ = LDBCommandExecuteResult::Failed("failed to open SST file: " +
|
||||
status.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
int bad_lines = 0;
|
||||
std::string line;
|
||||
std::ifstream ifs_stdin("/dev/stdin");
|
||||
std::istream* istream_p = ifs_stdin.is_open() ? &ifs_stdin : &std::cin;
|
||||
while (getline(*istream_p, line, '\n')) {
|
||||
std::string key;
|
||||
std::string value;
|
||||
if (ParseKeyValue(line, &key, &value, is_key_hex_, is_value_hex_)) {
|
||||
status = sst_file_writer.Put(key, value);
|
||||
if (!status.ok()) {
|
||||
exec_state_ = LDBCommandExecuteResult::Failed(
|
||||
"failed to write record to file: " + status.ToString());
|
||||
return;
|
||||
}
|
||||
} else if (0 == line.find("Keys in range:")) {
|
||||
// ignore this line
|
||||
} else if (0 == line.find("Created bg thread 0x")) {
|
||||
// ignore this line
|
||||
} else {
|
||||
bad_lines++;
|
||||
}
|
||||
}
|
||||
|
||||
status = sst_file_writer.Finish();
|
||||
if (!status.ok()) {
|
||||
exec_state_ = LDBCommandExecuteResult::Failed(
|
||||
"Failed to finish writing to file: " + status.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
if (bad_lines > 0) {
|
||||
fprintf(stderr, "Warning: %d bad lines ignored.\n", bad_lines);
|
||||
}
|
||||
exec_state_ = LDBCommandExecuteResult::Succeed(
|
||||
"external SST file written to " + output_sst_path_);
|
||||
}
|
||||
|
||||
Options WriteExternalSstFilesCommand::PrepareOptionsForOpenDB() {
|
||||
Options opt = LDBCommand::PrepareOptionsForOpenDB();
|
||||
opt.create_if_missing = create_if_missing_;
|
||||
return opt;
|
||||
}
|
||||
|
||||
const std::string IngestExternalSstFilesCommand::ARG_MOVE_FILES = "move_files";
|
||||
const std::string IngestExternalSstFilesCommand::ARG_SNAPSHOT_CONSISTENCY =
|
||||
"snapshot_consistency";
|
||||
const std::string IngestExternalSstFilesCommand::ARG_ALLOW_GLOBAL_SEQNO =
|
||||
"allow_global_seqno";
|
||||
const std::string IngestExternalSstFilesCommand::ARG_ALLOW_BLOCKING_FLUSH =
|
||||
"allow_blocking_flush";
|
||||
const std::string IngestExternalSstFilesCommand::ARG_INGEST_BEHIND =
|
||||
"ingest_behind";
|
||||
|
||||
void IngestExternalSstFilesCommand::Help(std::string& ret) {
|
||||
ret.append(" ");
|
||||
ret.append(IngestExternalSstFilesCommand::Name());
|
||||
ret.append(" <input_sst_path>");
|
||||
ret.append(" [--" + ARG_MOVE_FILES + "] ");
|
||||
ret.append(" [--" + ARG_SNAPSHOT_CONSISTENCY + "] ");
|
||||
ret.append(" [--" + ARG_ALLOW_GLOBAL_SEQNO + "] ");
|
||||
ret.append(" [--" + ARG_ALLOW_BLOCKING_FLUSH + "] ");
|
||||
ret.append(" [--" + ARG_INGEST_BEHIND + "] ");
|
||||
ret.append("\n");
|
||||
}
|
||||
|
||||
IngestExternalSstFilesCommand::IngestExternalSstFilesCommand(
|
||||
const std::vector<std::string>& params,
|
||||
const std::map<std::string, std::string>& options,
|
||||
const std::vector<std::string>& flags)
|
||||
: LDBCommand(
|
||||
options, flags, false /* is_read_only */,
|
||||
BuildCmdLineOptions({ARG_MOVE_FILES, ARG_SNAPSHOT_CONSISTENCY,
|
||||
ARG_ALLOW_GLOBAL_SEQNO, ARG_CREATE_IF_MISSING,
|
||||
ARG_ALLOW_BLOCKING_FLUSH, ARG_INGEST_BEHIND})),
|
||||
move_files_(false),
|
||||
snapshot_consistency_(true),
|
||||
allow_global_seqno_(true),
|
||||
allow_blocking_flush_(true),
|
||||
ingest_behind_(false) {
|
||||
create_if_missing_ =
|
||||
IsFlagPresent(flags, ARG_CREATE_IF_MISSING) ||
|
||||
ParseBooleanOption(options, ARG_CREATE_IF_MISSING, false);
|
||||
move_files_ = IsFlagPresent(flags, ARG_MOVE_FILES) ||
|
||||
ParseBooleanOption(options, ARG_MOVE_FILES, false);
|
||||
snapshot_consistency_ =
|
||||
IsFlagPresent(flags, ARG_SNAPSHOT_CONSISTENCY) ||
|
||||
ParseBooleanOption(options, ARG_SNAPSHOT_CONSISTENCY, true);
|
||||
allow_global_seqno_ =
|
||||
IsFlagPresent(flags, ARG_ALLOW_GLOBAL_SEQNO) ||
|
||||
ParseBooleanOption(options, ARG_ALLOW_GLOBAL_SEQNO, true);
|
||||
allow_blocking_flush_ =
|
||||
IsFlagPresent(flags, ARG_ALLOW_BLOCKING_FLUSH) ||
|
||||
ParseBooleanOption(options, ARG_ALLOW_BLOCKING_FLUSH, true);
|
||||
ingest_behind_ = IsFlagPresent(flags, ARG_INGEST_BEHIND) ||
|
||||
ParseBooleanOption(options, ARG_INGEST_BEHIND, false);
|
||||
|
||||
if (params.size() != 1) {
|
||||
exec_state_ =
|
||||
LDBCommandExecuteResult::Failed("input SST path must be specified");
|
||||
} else {
|
||||
input_sst_path_ = params.at(0);
|
||||
}
|
||||
}
|
||||
|
||||
void IngestExternalSstFilesCommand::DoCommand() {
|
||||
if (!db_) {
|
||||
assert(GetExecuteState().IsFailed());
|
||||
return;
|
||||
}
|
||||
if (GetExecuteState().IsFailed()) {
|
||||
return;
|
||||
}
|
||||
ColumnFamilyHandle* cfh = GetCfHandle();
|
||||
IngestExternalFileOptions ifo;
|
||||
ifo.move_files = move_files_;
|
||||
ifo.snapshot_consistency = snapshot_consistency_;
|
||||
ifo.allow_global_seqno = allow_global_seqno_;
|
||||
ifo.allow_blocking_flush = allow_blocking_flush_;
|
||||
ifo.ingest_behind = ingest_behind_;
|
||||
Status status = db_->IngestExternalFile(cfh, {input_sst_path_}, ifo);
|
||||
if (!status.ok()) {
|
||||
exec_state_ = LDBCommandExecuteResult::Failed(
|
||||
"failed to ingest external SST: " + status.ToString());
|
||||
} else {
|
||||
exec_state_ =
|
||||
LDBCommandExecuteResult::Succeed("external SST files ingested");
|
||||
}
|
||||
}
|
||||
|
||||
Options IngestExternalSstFilesCommand::PrepareOptionsForOpenDB() {
|
||||
Options opt = LDBCommand::PrepareOptionsForOpenDB();
|
||||
opt.create_if_missing = create_if_missing_;
|
||||
return opt;
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
@@ -522,4 +522,55 @@ class RestoreCommand : public BackupableCommand {
|
||||
static void Help(std::string& ret);
|
||||
};
|
||||
|
||||
class WriteExternalSstFilesCommand : public LDBCommand {
|
||||
public:
|
||||
static std::string Name() { return "write_extern_sst"; }
|
||||
WriteExternalSstFilesCommand(
|
||||
const std::vector<std::string>& params,
|
||||
const std::map<std::string, std::string>& options,
|
||||
const std::vector<std::string>& flags);
|
||||
|
||||
virtual void DoCommand() override;
|
||||
|
||||
virtual bool NoDBOpen() override { return false; }
|
||||
|
||||
virtual Options PrepareOptionsForOpenDB() override;
|
||||
|
||||
static void Help(std::string& ret);
|
||||
|
||||
private:
|
||||
std::string output_sst_path_;
|
||||
};
|
||||
|
||||
class IngestExternalSstFilesCommand : public LDBCommand {
|
||||
public:
|
||||
static std::string Name() { return "ingest_extern_sst"; }
|
||||
IngestExternalSstFilesCommand(
|
||||
const std::vector<std::string>& params,
|
||||
const std::map<std::string, std::string>& options,
|
||||
const std::vector<std::string>& flags);
|
||||
|
||||
virtual void DoCommand() override;
|
||||
|
||||
virtual bool NoDBOpen() override { return false; }
|
||||
|
||||
virtual Options PrepareOptionsForOpenDB() override;
|
||||
|
||||
static void Help(std::string& ret);
|
||||
|
||||
private:
|
||||
std::string input_sst_path_;
|
||||
bool move_files_;
|
||||
bool snapshot_consistency_;
|
||||
bool allow_global_seqno_;
|
||||
bool allow_blocking_flush_;
|
||||
bool ingest_behind_;
|
||||
|
||||
static const std::string ARG_MOVE_FILES;
|
||||
static const std::string ARG_SNAPSHOT_CONSISTENCY;
|
||||
static const std::string ARG_ALLOW_GLOBAL_SEQNO;
|
||||
static const std::string ARG_ALLOW_BLOCKING_FLUSH;
|
||||
static const std::string ARG_INGEST_BEHIND;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
+42
-1
@@ -76,7 +76,7 @@ class LDBTestCase(unittest.TestCase):
|
||||
|
||||
my_check_output("./ldb %s >/dev/null 2>&1 |grep -v \"Created bg \
|
||||
thread\"" % params, shell=True)
|
||||
except Exception, e:
|
||||
except Exception:
|
||||
return
|
||||
self.fail(
|
||||
"Exception should have been raised for command with params: %s" %
|
||||
@@ -146,6 +146,14 @@ class LDBTestCase(unittest.TestCase):
|
||||
def loadDb(self, params, dumpFile):
|
||||
return 0 == run_err_null("cat %s | ./ldb load %s" % (dumpFile, params))
|
||||
|
||||
def writeExternSst(self, params, inputDumpFile, outputSst):
|
||||
return 0 == run_err_null("cat %s | ./ldb write_extern_sst %s %s"
|
||||
% (inputDumpFile, outputSst, params))
|
||||
|
||||
def ingestExternSst(self, params, inputSst):
|
||||
return 0 == run_err_null("./ldb ingest_extern_sst %s %s"
|
||||
% (inputSst, params))
|
||||
|
||||
def testStringBatchPut(self):
|
||||
print "Running testStringBatchPut..."
|
||||
self.assertRunOK("batchput x1 y1 --create_if_missing", "OK")
|
||||
@@ -547,5 +555,38 @@ class LDBTestCase(unittest.TestCase):
|
||||
# non-existing column family.
|
||||
self.assertRunFAIL("get cf3_1 --column_family=four")
|
||||
|
||||
def testIngestExternalSst(self):
|
||||
print "Running testIngestExternalSst..."
|
||||
|
||||
# Dump, load, write external sst and ingest it in another db
|
||||
dbPath = os.path.join(self.TMP_DIR, "db1")
|
||||
self.assertRunOK(
|
||||
"batchput --db=%s --create_if_missing x1 y1 x2 y2 x3 y3 x4 y4"
|
||||
% dbPath,
|
||||
"OK")
|
||||
self.assertRunOK("scan --db=%s" % dbPath,
|
||||
"x1 : y1\nx2 : y2\nx3 : y3\nx4 : y4")
|
||||
dumpFilePath = os.path.join(self.TMP_DIR, "dump1")
|
||||
with open(dumpFilePath, 'w') as f:
|
||||
f.write("x1 ==> y10\nx2 ==> y20\nx3 ==> y30\nx4 ==> y40")
|
||||
externSstPath = os.path.join(self.TMP_DIR, "extern_data1.sst")
|
||||
self.assertTrue(self.writeExternSst("--create_if_missing --db=%s"
|
||||
% dbPath,
|
||||
dumpFilePath,
|
||||
externSstPath))
|
||||
# cannot ingest if allow_global_seqno is false
|
||||
self.assertFalse(
|
||||
self.ingestExternSst(
|
||||
"--create_if_missing --allow_global_seqno=false --db=%s"
|
||||
% dbPath,
|
||||
externSstPath))
|
||||
self.assertTrue(
|
||||
self.ingestExternSst(
|
||||
"--create_if_missing --allow_global_seqno --db=%s"
|
||||
% dbPath,
|
||||
externSstPath))
|
||||
self.assertRunOKFull("scan --db=%s" % dbPath,
|
||||
"x1 : y10\nx2 : y20\nx3 : y30\nx4 : y40")
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -88,6 +88,8 @@ void LDBCommandRunner::PrintHelp(const LDBOptions& ldb_options,
|
||||
BackupCommand::Help(ret);
|
||||
RestoreCommand::Help(ret);
|
||||
CheckPointCommand::Help(ret);
|
||||
WriteExternalSstFilesCommand::Help(ret);
|
||||
IngestExternalSstFilesCommand::Help(ret);
|
||||
|
||||
fprintf(stderr, "%s\n", ret.c_str());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user