@nxtedition/rocksdb 15.1.2 → 15.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/.claude/settings.local.json +15 -0
  2. package/binding.cc +79 -38
  3. package/build.sh +1 -2
  4. package/deps/rocksdb/rocksdb/BUCK +10 -8
  5. package/deps/rocksdb/rocksdb/CMakeLists.txt +27 -2
  6. package/deps/rocksdb/rocksdb/Makefile +27 -116
  7. package/deps/rocksdb/rocksdb/cache/cache_bench_tool.cc +1 -1
  8. package/deps/rocksdb/rocksdb/cache/clock_cache.cc +101 -124
  9. package/deps/rocksdb/rocksdb/cache/clock_cache.h +47 -30
  10. package/deps/rocksdb/rocksdb/db/c.cc +793 -131
  11. package/deps/rocksdb/rocksdb/db/c_test.c +571 -0
  12. package/deps/rocksdb/rocksdb/db/compact_files_test.cc +226 -0
  13. package/deps/rocksdb/rocksdb/db/compaction/compaction.h +4 -0
  14. package/deps/rocksdb/rocksdb/db/compaction/compaction_job.cc +95 -59
  15. package/deps/rocksdb/rocksdb/db/compaction/compaction_job.h +2 -2
  16. package/deps/rocksdb/rocksdb/db/compaction/compaction_job_test.cc +45 -35
  17. package/deps/rocksdb/rocksdb/db/compaction/compaction_outputs.cc +8 -4
  18. package/deps/rocksdb/rocksdb/db/compaction/compaction_outputs.h +1 -1
  19. package/deps/rocksdb/rocksdb/db/compaction/compaction_picker.cc +11 -6
  20. package/deps/rocksdb/rocksdb/db/compaction/compaction_picker.h +8 -2
  21. package/deps/rocksdb/rocksdb/db/compaction/compaction_picker_test.cc +47 -0
  22. package/deps/rocksdb/rocksdb/db/compaction/compaction_picker_universal.cc +12 -2
  23. package/deps/rocksdb/rocksdb/db/compaction/compaction_service_test.cc +82 -0
  24. package/deps/rocksdb/rocksdb/db/compaction/subcompaction_state.cc +2 -2
  25. package/deps/rocksdb/rocksdb/db/compaction/subcompaction_state.h +1 -1
  26. package/deps/rocksdb/rocksdb/db/db_basic_test.cc +69 -24
  27. package/deps/rocksdb/rocksdb/db/db_bloom_filter_test.cc +9 -1
  28. package/deps/rocksdb/rocksdb/db/db_compaction_test.cc +65 -0
  29. package/deps/rocksdb/rocksdb/db/db_etc3_test.cc +161 -0
  30. package/deps/rocksdb/rocksdb/db/db_filesnapshot.cc +1 -0
  31. package/deps/rocksdb/rocksdb/db/db_impl/db_impl.cc +20 -7
  32. package/deps/rocksdb/rocksdb/db/db_impl/db_impl.h +13 -0
  33. package/deps/rocksdb/rocksdb/db/db_impl/db_impl_compaction_flush.cc +114 -39
  34. package/deps/rocksdb/rocksdb/db/db_impl/db_impl_files.cc +3 -0
  35. package/deps/rocksdb/rocksdb/db/db_impl/db_impl_follower.cc +3 -3
  36. package/deps/rocksdb/rocksdb/db/db_impl/db_impl_open.cc +1 -1
  37. package/deps/rocksdb/rocksdb/db/db_impl/db_impl_secondary.cc +39 -25
  38. package/deps/rocksdb/rocksdb/db/db_iterator_test.cc +361 -0
  39. package/deps/rocksdb/rocksdb/db/db_options_test.cc +35 -0
  40. package/deps/rocksdb/rocksdb/db/db_range_del_test.cc +83 -0
  41. package/deps/rocksdb/rocksdb/db/db_test.cc +249 -4
  42. package/deps/rocksdb/rocksdb/db/db_test2.cc +3 -0
  43. package/deps/rocksdb/rocksdb/db/db_test_util.cc +2 -1
  44. package/deps/rocksdb/rocksdb/db/db_wal_test.cc +3 -2
  45. package/deps/rocksdb/rocksdb/db/flush_job_test.cc +7 -7
  46. package/deps/rocksdb/rocksdb/db/listener_test.cc +7 -17
  47. package/deps/rocksdb/rocksdb/db/memtable_list_test.cc +4 -2
  48. package/deps/rocksdb/rocksdb/db/obsolete_files_test.cc +41 -0
  49. package/deps/rocksdb/rocksdb/db/repair.cc +2 -2
  50. package/deps/rocksdb/rocksdb/db/version_edit.h +7 -4
  51. package/deps/rocksdb/rocksdb/db/version_set.cc +299 -90
  52. package/deps/rocksdb/rocksdb/db/version_set.h +56 -9
  53. package/deps/rocksdb/rocksdb/db/version_set_test.cc +41 -39
  54. package/deps/rocksdb/rocksdb/db/version_util.h +3 -2
  55. package/deps/rocksdb/rocksdb/db/wal_manager.cc +7 -1
  56. package/deps/rocksdb/rocksdb/db/wal_manager_test.cc +48 -10
  57. package/deps/rocksdb/rocksdb/db_stress_tool/db_stress_common.h +1 -0
  58. package/deps/rocksdb/rocksdb/db_stress_tool/db_stress_gflags.cc +5 -1
  59. package/deps/rocksdb/rocksdb/db_stress_tool/db_stress_test_base.cc +16 -5
  60. package/deps/rocksdb/rocksdb/env/env_test.cc +126 -41
  61. package/deps/rocksdb/rocksdb/env/fs_posix.cc +14 -7
  62. package/deps/rocksdb/rocksdb/env/io_posix.cc +304 -112
  63. package/deps/rocksdb/rocksdb/env/io_posix.h +16 -4
  64. package/deps/rocksdb/rocksdb/env/io_posix_test.cc +43 -0
  65. package/deps/rocksdb/rocksdb/folly.mk +148 -0
  66. package/deps/rocksdb/rocksdb/include/rocksdb/advanced_compression.h +29 -3
  67. package/deps/rocksdb/rocksdb/include/rocksdb/advanced_options.h +73 -0
  68. package/deps/rocksdb/rocksdb/include/rocksdb/c.h +246 -0
  69. package/deps/rocksdb/rocksdb/include/rocksdb/compaction_filter.h +0 -2
  70. package/deps/rocksdb/rocksdb/include/rocksdb/data_structure.h +15 -9
  71. package/deps/rocksdb/rocksdb/include/rocksdb/db.h +19 -9
  72. package/deps/rocksdb/rocksdb/include/rocksdb/env.h +1 -1
  73. package/deps/rocksdb/rocksdb/include/rocksdb/file_system.h +6 -4
  74. package/deps/rocksdb/rocksdb/include/rocksdb/metadata.h +14 -0
  75. package/deps/rocksdb/rocksdb/include/rocksdb/options.h +67 -6
  76. package/deps/rocksdb/rocksdb/include/rocksdb/sst_file_writer.h +1 -7
  77. package/deps/rocksdb/rocksdb/include/rocksdb/statistics.h +3 -0
  78. package/deps/rocksdb/rocksdb/include/rocksdb/thread_status.h +6 -14
  79. package/deps/rocksdb/rocksdb/include/rocksdb/utilities/backup_engine.h +8 -1
  80. package/deps/rocksdb/rocksdb/include/rocksdb/utilities/env_mirror.h +2 -2
  81. package/deps/rocksdb/rocksdb/include/rocksdb/utilities/ldb_cmd_execute_result.h +0 -4
  82. package/deps/rocksdb/rocksdb/include/rocksdb/utilities/option_change_migration.h +33 -5
  83. package/deps/rocksdb/rocksdb/include/rocksdb/utilities/stackable_db.h +6 -0
  84. package/deps/rocksdb/rocksdb/include/rocksdb/version.h +2 -2
  85. package/deps/rocksdb/rocksdb/monitoring/statistics.cc +2 -0
  86. package/deps/rocksdb/rocksdb/monitoring/thread_status_impl.cc +5 -2
  87. package/deps/rocksdb/rocksdb/monitoring/thread_status_updater.cc +2 -2
  88. package/deps/rocksdb/rocksdb/monitoring/thread_status_updater.h +6 -6
  89. package/deps/rocksdb/rocksdb/monitoring/thread_status_updater_debug.cc +2 -2
  90. package/deps/rocksdb/rocksdb/monitoring/thread_status_util.cc +10 -5
  91. package/deps/rocksdb/rocksdb/monitoring/thread_status_util.h +2 -2
  92. package/deps/rocksdb/rocksdb/options/cf_options.cc +15 -3
  93. package/deps/rocksdb/rocksdb/options/cf_options.h +7 -0
  94. package/deps/rocksdb/rocksdb/options/db_options.cc +27 -36
  95. package/deps/rocksdb/rocksdb/options/db_options.h +3 -2
  96. package/deps/rocksdb/rocksdb/options/options.cc +4 -0
  97. package/deps/rocksdb/rocksdb/options/options_helper.cc +8 -2
  98. package/deps/rocksdb/rocksdb/options/options_settable_test.cc +4 -1
  99. package/deps/rocksdb/rocksdb/options/options_test.cc +19 -3
  100. package/deps/rocksdb/rocksdb/src.mk +1 -1
  101. package/deps/rocksdb/rocksdb/table/block_based/block_based_table_builder.cc +155 -32
  102. package/deps/rocksdb/rocksdb/table/block_based/block_based_table_builder.h +7 -3
  103. package/deps/rocksdb/rocksdb/table/block_based/block_based_table_iterator.cc +169 -125
  104. package/deps/rocksdb/rocksdb/table/block_based/block_based_table_iterator.h +22 -7
  105. package/deps/rocksdb/rocksdb/table/block_based/block_based_table_reader.cc +43 -24
  106. package/deps/rocksdb/rocksdb/table/block_based/block_based_table_reader.h +9 -5
  107. package/deps/rocksdb/rocksdb/table/block_based/block_based_table_reader_test.cc +9 -8
  108. package/deps/rocksdb/rocksdb/table/block_based/filter_block.h +17 -0
  109. package/deps/rocksdb/rocksdb/table/block_based/filter_policy.cc +15 -5
  110. package/deps/rocksdb/rocksdb/table/block_based/filter_policy_internal.h +13 -18
  111. package/deps/rocksdb/rocksdb/table/block_based/full_filter_block.cc +29 -0
  112. package/deps/rocksdb/rocksdb/table/block_based/full_filter_block.h +6 -0
  113. package/deps/rocksdb/rocksdb/table/block_based/full_filter_block_test.cc +15 -0
  114. package/deps/rocksdb/rocksdb/table/block_based/index_builder.cc +79 -19
  115. package/deps/rocksdb/rocksdb/table/block_based/index_builder.h +48 -20
  116. package/deps/rocksdb/rocksdb/table/block_based/partitioned_filter_block.cc +51 -0
  117. package/deps/rocksdb/rocksdb/table/block_based/partitioned_filter_block.h +19 -0
  118. package/deps/rocksdb/rocksdb/table/block_based/user_defined_index_wrapper.h +1 -1
  119. package/deps/rocksdb/rocksdb/table/external_table.cc +2 -2
  120. package/deps/rocksdb/rocksdb/table/sst_file_dumper.cc +3 -2
  121. package/deps/rocksdb/rocksdb/table/sst_file_dumper.h +3 -1
  122. package/deps/rocksdb/rocksdb/table/table_builder.h +5 -0
  123. package/deps/rocksdb/rocksdb/table/table_reader.h +4 -2
  124. package/deps/rocksdb/rocksdb/table/table_test.cc +48 -39
  125. package/deps/rocksdb/rocksdb/test_util/sync_point.cc +4 -0
  126. package/deps/rocksdb/rocksdb/test_util/sync_point.h +32 -0
  127. package/deps/rocksdb/rocksdb/test_util/testutil.h +6 -2
  128. package/deps/rocksdb/rocksdb/tools/db_bench_tool.cc +14 -4
  129. package/deps/rocksdb/rocksdb/tools/ldb_cmd.cc +8 -5
  130. package/deps/rocksdb/rocksdb/tools/ldb_cmd_test.cc +3 -2
  131. package/deps/rocksdb/rocksdb/tools/sst_dump_tool.cc +63 -12
  132. package/deps/rocksdb/rocksdb/util/auto_tune_compressor.cc +16 -1
  133. package/deps/rocksdb/rocksdb/util/auto_tune_compressor.h +5 -1
  134. package/deps/rocksdb/rocksdb/util/bit_fields.h +133 -23
  135. package/deps/rocksdb/rocksdb/util/bloom_test.cc +2 -5
  136. package/deps/rocksdb/rocksdb/util/compression.cc +51 -23
  137. package/deps/rocksdb/rocksdb/util/compression_test.cc +525 -270
  138. package/deps/rocksdb/rocksdb/util/filter_bench.cc +3 -4
  139. package/deps/rocksdb/rocksdb/util/simple_mixed_compressor.cc +11 -2
  140. package/deps/rocksdb/rocksdb/util/simple_mixed_compressor.h +4 -1
  141. package/deps/rocksdb/rocksdb/util/slice_test.cc +92 -0
  142. package/deps/rocksdb/rocksdb/util/thread_list_test.cc +2 -2
  143. package/deps/rocksdb/rocksdb/util/thread_operation.h +2 -2
  144. package/deps/rocksdb/rocksdb/util/threadpool_imp.cc +2 -2
  145. package/deps/rocksdb/rocksdb/utilities/backup/backup_engine.cc +19 -2
  146. package/deps/rocksdb/rocksdb/utilities/backup/backup_engine_test.cc +75 -0
  147. package/deps/rocksdb/rocksdb/utilities/checkpoint/checkpoint_test.cc +1 -0
  148. package/deps/rocksdb/rocksdb/utilities/option_change_migration/option_change_migration.cc +303 -111
  149. package/deps/rocksdb/rocksdb/utilities/option_change_migration/option_change_migration_test.cc +379 -0
  150. package/deps/rocksdb/rocksdb.gyp +1 -0
  151. package/iterator.js +66 -70
  152. package/package.json +6 -6
  153. package/prebuilds/darwin-arm64/@nxtedition+rocksdb.node +0 -0
  154. package/deps/rocksdb/rocksdb/table/block_based/index_builder_test.cc +0 -183
@@ -1045,13 +1045,6 @@ void BlockBasedTableIterator::Prepare(const MultiScanArgs* multiscan_opts) {
1045
1045
  }
1046
1046
 
1047
1047
  void BlockBasedTableIterator::SeekMultiScan(const Slice* seek_target) {
1048
- if (SeekMultiScanImpl(seek_target)) {
1049
- is_out_of_bound_ = true;
1050
- assert(!Valid());
1051
- }
1052
- }
1053
-
1054
- bool BlockBasedTableIterator::SeekMultiScanImpl(const Slice* seek_target) {
1055
1048
  assert(multi_scan_ && multi_scan_status_.ok());
1056
1049
  // This is a MultiScan and Preapre() has been called.
1057
1050
 
@@ -1063,49 +1056,59 @@ bool BlockBasedTableIterator::SeekMultiScanImpl(const Slice* seek_target) {
1063
1056
  if (!seek_target) {
1064
1057
  // start key must be set for multi-scan
1065
1058
  multi_scan_status_ = Status::InvalidArgument("No seek key for MultiScan");
1066
- return false;
1059
+ return;
1067
1060
  }
1068
1061
 
1069
- constexpr auto out_of_bound = true;
1070
-
1071
1062
  // Check the case where there is no range prepared on this table
1072
1063
  if (multi_scan_->scan_opts->size() == 0) {
1073
1064
  // out of bound
1074
- return out_of_bound;
1065
+ MarkPreparedRangeExhausted();
1066
+ return;
1075
1067
  }
1076
1068
 
1077
1069
  // Check whether seek key is moving forward.
1078
- if (!multi_scan_->prev_seek_key_.empty()) {
1079
- if (user_comparator_.CompareWithoutTimestamp(ExtractUserKey(*seek_target),
1080
- /*a_has_ts=*/true,
1081
- multi_scan_->prev_seek_key_,
1082
- /*b_has_ts=*/false) < 0) {
1083
- // The seek target moved backward
1084
- multi_scan_status_ =
1085
- Status::InvalidArgument("Unexpected seek key moving backward");
1086
- return false;
1087
- }
1088
- }
1089
- multi_scan_->prev_seek_key_ = ExtractUserKey(*seek_target).ToString();
1090
-
1091
- // There are still a few cases we need to handle
1092
- // table: _____[prepared range 1]_____[prepared range 2]_____
1093
- // seek : 1 2 3 4 5
1094
- // Case 1: seek before the first prepared ranges, return out of bound
1095
- // Case 2: seek at the beginning of a prepared range (expected case)
1096
- // Case 3: seek within a prepared range (unexpected, but supported)
1097
- // Case 4: seek between 2 of the prepared ranges, return out of bound
1098
- // Case 5: seek after all of the prepared ranges, should move on to next file
1099
- // The reason this could happen is due to seek key adjustment due to delete
1100
- // range file.
1101
- // E.g. LSM has 3 levels, each level has only 1 file:
1102
- // L1 : key : 0---10
1103
- // L2 : Delete range key : 0-5
1104
- // L3 : key : 0---10
1105
- // When a range 2-8 was prepared, the prepared key would be 2 on L3 file, but
1106
- // the seek key would be 5, as the seek key was updated by the largest key of
1107
- // delete range. This causes all of the cases above to be possible, when the
1108
- // ranges are adjusted in the above examples.
1070
+ if (multi_scan_->prev_seek_key_.empty() ||
1071
+ icomp_.Compare(*seek_target, multi_scan_->prev_seek_key_) > 0) {
1072
+ // If seek key is empty or is larger than previous seek key, update the
1073
+ // previous seek key. Otherwise use the previous seek key as the adjusted
1074
+ // seek target moving forward. This prevents seek target going backward,
1075
+ // which would visit pages that have been unpinned.
1076
+ // This issue is caused by sub-optimal range delete handling inside merge
1077
+ // iterator.
1078
+ // TODO xingbo issues:14068 : Optimize the handling of range delete iterator
1079
+ // inside merge iterator, so that it doesn't move seek key backward. After
1080
+ // that we could return error if the key moves backward here.
1081
+ multi_scan_->prev_seek_key_ = seek_target->ToString();
1082
+ } else {
1083
+ // Seek key is adjusted to previous one, we can return here directly.
1084
+ return;
1085
+ }
1086
+
1087
+ // There are 3 different Cases we need to handle:
1088
+ // The following diagram explain different seek targets seeking at various
1089
+ // position on the table, while the next_scan_idx points to the PreparedRange
1090
+ // 2.
1091
+ //
1092
+ // next_scan_idx: -------------------┐
1093
+ //
1094
+ // table: : __[PreparedRange 1]__[PreparedRange 2]__[PreparedRange 3]__
1095
+ // Seek target: <----- Case 1 ------>▲<------------- Case 2 -------------->
1096
+ //
1097
+ // Case 3
1098
+ //
1099
+ // Case 1: seek before the start of next prepared ranges. This could happen
1100
+ // due to too many delete tomestone triggered reseek or delete range.
1101
+ // Case 2: seek after the start of next prepared range.
1102
+ // This could happen due to seek key adjustment from delete range file.
1103
+ // E.g. LSM has 3 levels, each level has only 1 file:
1104
+ // L1 : key : 0---10
1105
+ // L2 : Delete range key : 0-5
1106
+ // L3 : key : 0---10
1107
+ // When a range 2-8 was prepared, the prepared key would be 2 on L3 file,
1108
+ // but the seek key would be 5, as the seek key was updated by the largest
1109
+ // key of delete range. This causes all of the cases above to be possible,
1110
+ // when the ranges are adjusted in the above examples.
1111
+ // Case 3: seek at the beginning of a prepared range (expected case)
1109
1112
 
1110
1113
  // Allow reseek on the start of the last prepared range due to too many
1111
1114
  // tombstone
@@ -1113,83 +1116,151 @@ bool BlockBasedTableIterator::SeekMultiScanImpl(const Slice* seek_target) {
1113
1116
  std::min(multi_scan_->next_scan_idx,
1114
1117
  multi_scan_->block_index_ranges_per_scan.size() - 1);
1115
1118
 
1119
+ auto user_seek_target = ExtractUserKey(*seek_target);
1120
+
1116
1121
  auto compare_next_scan_start_result =
1117
1122
  user_comparator_.CompareWithoutTimestamp(
1118
- ExtractUserKey(*seek_target), /*a_has_ts=*/true,
1123
+ user_seek_target, /*a_has_ts=*/true,
1119
1124
  multi_scan_->scan_opts->GetScanRanges()[multi_scan_->next_scan_idx]
1120
1125
  .range.start.value(),
1121
1126
  /*b_has_ts=*/false);
1122
1127
 
1123
1128
  if (compare_next_scan_start_result != 0) {
1124
- // The seek key is not exactly same as what was prepared.
1129
+ // The seek target is not exactly same as what was prepared.
1125
1130
  if (compare_next_scan_start_result < 0) {
1126
- // Needs to handle Cases: 1, 3, 4
1127
- //
1128
- // next_scan_idx : |
1129
- // V
1130
- // table: _____[prepared range 1]_____[prepared range 2]_____
1131
- // seek : 1 3 4
1132
-
1133
- // Case 1: Seek key is before the start key of the first range
1131
+ // Case 1:
1134
1132
  if (multi_scan_->next_scan_idx == 0) {
1135
- return out_of_bound;
1133
+ // This should not happen, even when seek target is adjusted by delete
1134
+ // range. The reason is that if the seek target is before the start key
1135
+ // of the first prepared range, its end key needs to be >= the smallest
1136
+ // key of this file, otherwise it is skipped in level iterator. If its
1137
+ // end key is >= the smallest key of this file, then this range will be
1138
+ // prepared for this file. As delete range could only adjust seek
1139
+ // target forward, so it would never be before the start key of the
1140
+ // first prepared range.
1141
+ assert(false && "Seek target before the first prepared range");
1142
+ MarkPreparedRangeExhausted();
1143
+ return;
1136
1144
  }
1137
- // Case: 3, 4
1138
- MultiScanUnexpectedSeekTarget(
1139
- seek_target, std::get<0>(multi_scan_->block_index_ranges_per_scan
1140
- [multi_scan_->next_scan_idx - 1]));
1141
-
1145
+ auto seek_target_before_previous_prepared_range =
1146
+ user_comparator_.CompareWithoutTimestamp(
1147
+ user_seek_target, /*a_has_ts=*/true,
1148
+ multi_scan_->scan_opts
1149
+ ->GetScanRanges()[multi_scan_->next_scan_idx - 1]
1150
+ .range.start.value(),
1151
+ /*b_has_ts=*/false) < 0;
1152
+ // Not expected to happen
1153
+ // This should never happen, the reason is that the
1154
+ // multi_scan_->next_scan_idx is set to a non zero value is due to a seek
1155
+ // target larger or equal to the start key of multi_scan_->next_scan_idx-1
1156
+ // happended earlier. If a seek happens before the start key of
1157
+ // multi_scan_->next_scan_idx-1, it would seek a key that is less than
1158
+ // what was seeked before.
1159
+ assert(!seek_target_before_previous_prepared_range);
1160
+ if (seek_target_before_previous_prepared_range) {
1161
+ multi_scan_status_ = Status::InvalidArgument(
1162
+ "Seek target is before the previous prepared range at index " +
1163
+ std::to_string(multi_scan_->next_scan_idx));
1164
+ return;
1165
+ }
1166
+ // It should only be possible to seek a key between the start of current
1167
+ // prepared scan and start of next prepared range.
1168
+ MultiScanUnexpectedSeekTarget(seek_target, &user_seek_target);
1142
1169
  } else {
1143
- // Needs to handle Cases: 3, 4, 5
1144
- // next_scan_idx :|
1145
- // V
1146
- // table: ____[prepared range 1]_____[prepared range 2]_____
1147
- // seek : 3 4 5
1148
- MultiScanUnexpectedSeekTarget(
1149
- seek_target,
1150
- std::get<0>(
1151
- multi_scan_
1152
- ->block_index_ranges_per_scan[multi_scan_->next_scan_idx]));
1170
+ // Case 2:
1171
+ MultiScanUnexpectedSeekTarget(seek_target, &user_seek_target);
1153
1172
  }
1154
1173
  } else {
1155
- if (multi_scan_->next_scan_idx >=
1156
- multi_scan_->block_index_ranges_per_scan.size()) {
1157
- // Seeking a range that is out side of prepared ranges.
1158
- return out_of_bound;
1159
- }
1174
+ // Case 2:
1175
+ assert(multi_scan_->next_scan_idx <
1176
+ multi_scan_->block_index_ranges_per_scan.size());
1160
1177
 
1161
1178
  auto [cur_scan_start_idx, cur_scan_end_idx] =
1162
1179
  multi_scan_->block_index_ranges_per_scan[multi_scan_->next_scan_idx];
1163
1180
  // We should have the data block already loaded
1164
1181
  ++multi_scan_->next_scan_idx;
1165
1182
  if (cur_scan_start_idx >= cur_scan_end_idx) {
1166
- if (multi_scan_->next_scan_idx <
1167
- multi_scan_->block_index_ranges_per_scan.size()) {
1168
- return out_of_bound;
1169
- } else {
1170
- ResetDataIter();
1171
- return false;
1172
- }
1173
- } else {
1174
- is_out_of_bound_ = false;
1183
+ // No blocks are prepared for this range at current file.
1184
+ MarkPreparedRangeExhausted();
1185
+ return;
1175
1186
  }
1176
1187
 
1177
- MultiScanSeekTargetFromBlock(seek_target, cur_scan_start_idx);
1188
+ // max_sequential_skip_in_iterations can trigger a reseek on the start
1189
+ // key of a scan range, even though the multiscan is already past
1190
+ // `cur_scan_start_idx` (e.g., a user key spans multiple data blocks).
1191
+ size_t block_idx =
1192
+ std::max(cur_scan_start_idx, multi_scan_->cur_data_block_idx);
1193
+ MultiScanSeekTargetFromBlock(seek_target, block_idx);
1178
1194
  }
1179
-
1180
- return false;
1181
1195
  }
1182
1196
 
1183
1197
  void BlockBasedTableIterator::MultiScanUnexpectedSeekTarget(
1184
- const Slice* seek_target, size_t block_idx) {
1198
+ const Slice* seek_target, const Slice* user_seek_target) {
1185
1199
  // linear search the block that contains the seek target, and unpin blocks
1186
1200
  // that are before it.
1201
+
1202
+ // The logic here could be confusing when there is a delete range involved.
1203
+ // E.g. we have an LSM with 3 levels, each level has only 1 file:
1204
+ // L1: data file : 0---10
1205
+ // L2: Delete range : 0-5
1206
+ // L3: data file : 0---10
1207
+ //
1208
+ // MultiScan on ranges 1-2, 3-4, and 5-6.
1209
+ // When user first do Seek(1), on level 2, due to delete range 0-5, the seek
1210
+ // key is adjusted to 5 at level 3. Therefore, we will internally do Seek(5)
1211
+ // and unpins all blocks until 5 at level 3. Then the next scan's blocks from
1212
+ // 3-4 are unpinned at level 3. It is confusing that maybe block 3-4 should
1213
+ // not be unpinned, as next scan would need it. But Seek(5) implies that these
1214
+ // keys are all covered by some range deletion, so the next Seek(3) will also
1215
+ // do Seek(5) internally, so the blocks from 3-4 could be safely unpinned.
1216
+
1217
+ // advance to the right prepared range
1218
+ while (
1219
+ multi_scan_->next_scan_idx <
1220
+ multi_scan_->block_index_ranges_per_scan.size() &&
1221
+ (user_comparator_.CompareWithoutTimestamp(
1222
+ *user_seek_target, /*a_has_ts=*/true,
1223
+ multi_scan_->scan_opts->GetScanRanges()[multi_scan_->next_scan_idx]
1224
+ .range.start.value(),
1225
+ /*b_has_ts=*/false) >= 0)) {
1226
+ multi_scan_->next_scan_idx++;
1227
+ }
1228
+
1229
+ // next_scan_idx is guaranteed to be higher than 0. If the seek key is before
1230
+ // the start key of first prepared range, it is already handled by caller
1231
+ // SeekMultiScan. It is equal, it would not call this funciton. If it is
1232
+ // after, next_scan_idx would be advanced by the loop above.
1233
+ assert(multi_scan_->next_scan_idx > 0);
1234
+ // Get the current range
1235
+ auto cur_scan_idx = multi_scan_->next_scan_idx - 1;
1236
+ auto [cur_scan_start_idx, cur_scan_end_idx] =
1237
+ multi_scan_->block_index_ranges_per_scan[cur_scan_idx];
1238
+
1239
+ if (cur_scan_start_idx >= cur_scan_end_idx) {
1240
+ // No blocks are prepared for this range at current file.
1241
+ MarkPreparedRangeExhausted();
1242
+ return;
1243
+ }
1244
+
1245
+ // Unpin all the blocks from multi_scan_->cur_data_block_idx to
1246
+ // cur_scan_start_idx
1247
+ for (auto unpin_block_idx = multi_scan_->cur_data_block_idx;
1248
+ unpin_block_idx < cur_scan_start_idx; unpin_block_idx++) {
1249
+ if (!multi_scan_->pinned_data_blocks[unpin_block_idx].IsEmpty()) {
1250
+ multi_scan_->pinned_data_blocks[unpin_block_idx].Reset();
1251
+ }
1252
+ }
1253
+
1254
+ // Take the max here to ensure we don't move backwards.
1255
+ size_t block_idx =
1256
+ std::max(cur_scan_start_idx, multi_scan_->cur_data_block_idx);
1187
1257
  auto const& data_block_separators = multi_scan_->data_block_separators;
1188
1258
  while (block_idx < data_block_separators.size() &&
1189
1259
  (user_comparator_.CompareWithoutTimestamp(
1190
- ExtractUserKey(*seek_target), /*a_has_ts=*/true,
1260
+ *user_seek_target, /*a_has_ts=*/true,
1191
1261
  data_block_separators[block_idx],
1192
1262
  /*b_has_ts=*/false) > 0)) {
1263
+ // Unpin the blocks that are passed
1193
1264
  if (!multi_scan_->pinned_data_blocks[block_idx].IsEmpty()) {
1194
1265
  multi_scan_->pinned_data_blocks[block_idx].Reset();
1195
1266
  }
@@ -1197,30 +1268,11 @@ void BlockBasedTableIterator::MultiScanUnexpectedSeekTarget(
1197
1268
  }
1198
1269
 
1199
1270
  if (block_idx >= data_block_separators.size()) {
1200
- // Handle case 5, when seek key is larger than the last block in the last
1201
- // prepared range.
1202
- ResetDataIter();
1203
- assert(!Valid());
1271
+ // All of the prepared blocks for this file is exhausted.
1272
+ MarkPreparedRangeExhausted();
1204
1273
  return;
1205
1274
  }
1206
1275
 
1207
- // // The iterator from previous seek may have moved forward a few blocks,
1208
- // // In that case, have block_idx catch up the cur_data_block_idx
1209
- // // Note no need to handle block unpin, as it has been handled during
1210
- // iterating block_idx = std::max(block_idx, multi_scan_->cur_data_block_idx);
1211
-
1212
- // advance to the right prepared range
1213
- while (
1214
- multi_scan_->next_scan_idx <
1215
- multi_scan_->block_index_ranges_per_scan.size() &&
1216
- (user_comparator_.CompareWithoutTimestamp(
1217
- ExtractUserKey(*seek_target), /*a_has_ts=*/true,
1218
- multi_scan_->scan_opts->GetScanRanges()[multi_scan_->next_scan_idx]
1219
- .range.start.value(),
1220
- /*b_has_ts=*/false) >= 0)) {
1221
- multi_scan_->next_scan_idx++;
1222
- }
1223
-
1224
1276
  // The current block may contain the data for the target key
1225
1277
  MultiScanSeekTargetFromBlock(seek_target, block_idx);
1226
1278
  }
@@ -1257,6 +1309,7 @@ void BlockBasedTableIterator::MultiScanSeekTargetFromBlock(
1257
1309
  block_iter_points_to_real_block_ = true;
1258
1310
  block_iter_.Seek(*seek_target);
1259
1311
  FindKeyForward();
1312
+ CheckOutOfBound();
1260
1313
  }
1261
1314
 
1262
1315
  void BlockBasedTableIterator::FindBlockForwardInMultiScan() {
@@ -1275,20 +1328,7 @@ void BlockBasedTableIterator::FindBlockForwardInMultiScan() {
1275
1328
  // for this file, it may need to continue to scan into the next file, so
1276
1329
  // we do not set is_out_of_bound_ in this case.
1277
1330
  if (multi_scan_->cur_data_block_idx + 1 >= cur_scan_end_idx) {
1278
- if (multi_scan_->next_scan_idx >=
1279
- multi_scan_->block_index_ranges_per_scan.size()) {
1280
- // We are done with this file, should let LevelIter advance to the
1281
- // next file instead of ending the scan
1282
- ResetDataIter();
1283
- assert(!is_out_of_bound_);
1284
- assert(!Valid());
1285
- return;
1286
- }
1287
- // We don't ResetDataIter() here since next scan might be reading from
1288
- // the same block. ResetDataIter() will free the underlying block cache
1289
- // handle and we don't want the block to be unpinned.
1290
- is_out_of_bound_ = true;
1291
- assert(!Valid());
1331
+ MarkPreparedRangeExhausted();
1292
1332
  return;
1293
1333
  }
1294
1334
  // Move to the next pinned data block
@@ -1419,7 +1459,7 @@ Status BlockBasedTableIterator::CollectBlockHandles(
1419
1459
  std::vector<std::tuple<size_t, size_t>>* block_index_ranges_per_scan,
1420
1460
  std::vector<std::string>* data_block_separators) {
1421
1461
  // print file name and level
1422
- if (kVerbose) {
1462
+ if (UNLIKELY(kVerbose)) {
1423
1463
  auto file_name = table_->get_rep()->file->file_name();
1424
1464
  auto level = table_->get_rep()->level;
1425
1465
  printf("file name : %s, level %d\n", file_name.c_str(), level);
@@ -1480,11 +1520,16 @@ Status BlockBasedTableIterator::CollectBlockHandles(
1480
1520
  }
1481
1521
  block_index_ranges_per_scan->emplace_back(
1482
1522
  scan_block_handles->size() - num_blocks, scan_block_handles->size());
1483
- if (kVerbose) {
1523
+ if (UNLIKELY(kVerbose)) {
1484
1524
  printf("separators :");
1485
1525
  for (const auto& separator : *data_block_separators) {
1486
1526
  printf("%s, ", separator.c_str());
1487
1527
  }
1528
+ printf("\nblock_index_ranges_per_scan :");
1529
+ for (auto const& block_index_range : *block_index_ranges_per_scan) {
1530
+ printf("[%zu, %zu], ", std::get<0>(block_index_range),
1531
+ std::get<1>(block_index_range));
1532
+ }
1488
1533
  printf("\n");
1489
1534
  }
1490
1535
  }
@@ -1664,7 +1709,6 @@ Status BlockBasedTableIterator::ExecuteIO(
1664
1709
  assert(false);
1665
1710
  return s;
1666
1711
  }
1667
- assert(async_read.io_handle);
1668
1712
  for (auto& req : *read_reqs) {
1669
1713
  if (!req.status.ok()) {
1670
1714
  assert(false);
@@ -381,6 +381,27 @@ class BlockBasedTableIterator : public InternalIteratorBase<Slice> {
381
381
  bool block_iter_points_to_real_block_;
382
382
  // See InternalIteratorBase::IsOutOfBound().
383
383
  bool is_out_of_bound_ = false;
384
+
385
+ // Mark prepared ranges as exhausted for multiscan.
386
+ void MarkPreparedRangeExhausted() {
387
+ assert(multi_scan_ != nullptr);
388
+ if (multi_scan_->next_scan_idx <
389
+ multi_scan_->block_index_ranges_per_scan.size()) {
390
+ // If there are more prepared ranges, we don't ResetDataIter() here,
391
+ // because next scan might be reading from the same block. ResetDataIter()
392
+ // will free the underlying block cache handle and we don't want the
393
+ // block to be unpinned.
394
+ // Set out of bound to mark the current prepared range as exhausted.
395
+ is_out_of_bound_ = true;
396
+ } else {
397
+ // This is the last prepared range of this file, there might be more
398
+ // data on next file. Reset data iterator to indicate the iterator is
399
+ // no longer valid on this file. Let LevelIter advance to the next file
400
+ // instead of ending the scan.
401
+ ResetDataIter();
402
+ }
403
+ }
404
+
384
405
  // During cache lookup to find readahead size, index_iter_ is iterated and it
385
406
  // can point to a different block.
386
407
  // If Prepare() is called, index_iter_ is used to prefetch data blocks for the
@@ -612,12 +633,8 @@ class BlockBasedTableIterator : public InternalIteratorBase<Slice> {
612
633
 
613
634
  // *** BEGIN APIs relevant to multiscan ***
614
635
 
615
- // Wrapper for SeekMultiScanImpl for handling out of bound
616
636
  void SeekMultiScan(const Slice* target);
617
637
 
618
- // Return true if the result is out of bound
619
- bool SeekMultiScanImpl(const Slice* seek_target);
620
-
621
638
  void FindBlockForwardInMultiScan();
622
639
 
623
640
  void PrepareReadAsyncCallBack(FSReadRequest& req, void* cb_arg) {
@@ -635,14 +652,12 @@ class BlockBasedTableIterator : public InternalIteratorBase<Slice> {
635
652
  std::to_string(async_state->offset) + " and async callback " +
636
653
  std::to_string(req.offset));
637
654
  }
638
- } else {
639
- assert(async_state->status.IsAborted());
640
655
  }
641
656
  }
642
657
 
643
658
  void MultiScanSeekTargetFromBlock(const Slice* seek_target, size_t block_idx);
644
659
  void MultiScanUnexpectedSeekTarget(const Slice* seek_target,
645
- size_t block_idx);
660
+ const Slice* user_seek_target);
646
661
 
647
662
  // Return true, if there is an error, or end of file
648
663
  bool MultiScanLoadDataBlock(size_t idx) {
@@ -2705,7 +2705,7 @@ Status BlockBasedTable::Prefetch(const ReadOptions& read_options,
2705
2705
  }
2706
2706
  BlockCacheLookupContext lookup_context{TableReaderCaller::kPrefetch};
2707
2707
  IndexBlockIter iiter_on_stack;
2708
- auto iiter = NewIndexIterator(read_options, /*need_upper_bound_check=*/false,
2708
+ auto iiter = NewIndexIterator(read_options, /*disable_prefix_seek=*/false,
2709
2709
  &iiter_on_stack, /*get_context=*/nullptr,
2710
2710
  &lookup_context);
2711
2711
  std::unique_ptr<InternalIteratorBase<IndexValue>> iiter_unique_ptr;
@@ -2742,7 +2742,7 @@ Status BlockBasedTable::Prefetch(const ReadOptions& read_options,
2742
2742
  DataBlockIter biter;
2743
2743
  Status tmp_status;
2744
2744
  NewDataBlockIterator<DataBlockIter>(
2745
- read_options, block_handle, &biter, /*type=*/BlockType::kData,
2745
+ read_options, block_handle, &biter, /*block_type=*/BlockType::kData,
2746
2746
  /*get_context=*/nullptr, &lookup_context,
2747
2747
  /*prefetch_buffer=*/nullptr, /*for_compaction=*/false,
2748
2748
  /*async_read=*/false, tmp_status, /*use_block_cache_for_lookup=*/true);
@@ -2757,7 +2757,8 @@ Status BlockBasedTable::Prefetch(const ReadOptions& read_options,
2757
2757
  }
2758
2758
 
2759
2759
  Status BlockBasedTable::VerifyChecksum(const ReadOptions& read_options,
2760
- TableReaderCaller caller) {
2760
+ TableReaderCaller caller,
2761
+ bool meta_blocks_only) {
2761
2762
  Status s;
2762
2763
  // Check Meta blocks
2763
2764
  std::unique_ptr<Block> metaindex;
@@ -2772,6 +2773,9 @@ Status BlockBasedTable::VerifyChecksum(const ReadOptions& read_options,
2772
2773
  } else {
2773
2774
  return s;
2774
2775
  }
2776
+ if (meta_blocks_only) {
2777
+ return s;
2778
+ }
2775
2779
  // Check Data blocks
2776
2780
  IndexBlockIter iiter_on_stack;
2777
2781
  BlockCacheLookupContext context{caller};
@@ -2967,7 +2971,7 @@ bool BlockBasedTable::TEST_BlockInCache(const BlockHandle& handle) const {
2967
2971
  bool BlockBasedTable::TEST_KeyInCache(const ReadOptions& options,
2968
2972
  const Slice& key) {
2969
2973
  std::unique_ptr<InternalIteratorBase<IndexValue>> iiter(NewIndexIterator(
2970
- options, /*need_upper_bound_check=*/false, /*input_iter=*/nullptr,
2974
+ options, /*disable_prefix_seek=*/false, /*input_iter=*/nullptr,
2971
2975
  /*get_context=*/nullptr, /*lookup_context=*/nullptr));
2972
2976
  iiter->Seek(key);
2973
2977
  assert(iiter->status().ok());
@@ -3174,9 +3178,9 @@ bool BlockBasedTable::TEST_IndexBlockInCache() const {
3174
3178
  Status BlockBasedTable::GetKVPairsFromDataBlocks(
3175
3179
  const ReadOptions& read_options, std::vector<KVPairBlock>* kv_pair_blocks) {
3176
3180
  std::unique_ptr<InternalIteratorBase<IndexValue>> blockhandles_iter(
3177
- NewIndexIterator(read_options, /*need_upper_bound_check=*/false,
3181
+ NewIndexIterator(read_options, /*disable_prefix_seek=*/false,
3178
3182
  /*input_iter=*/nullptr, /*get_context=*/nullptr,
3179
- /*lookup_contex=*/nullptr));
3183
+ /*lookup_context=*/nullptr));
3180
3184
 
3181
3185
  Status s = blockhandles_iter->status();
3182
3186
  if (!s.ok()) {
@@ -3196,7 +3200,7 @@ Status BlockBasedTable::GetKVPairsFromDataBlocks(
3196
3200
  Status tmp_status;
3197
3201
  datablock_iter.reset(NewDataBlockIterator<DataBlockIter>(
3198
3202
  read_options, blockhandles_iter->value().handle,
3199
- /*input_iter=*/nullptr, /*type=*/BlockType::kData,
3203
+ /*input_iter=*/nullptr, /*block_type=*/BlockType::kData,
3200
3204
  /*get_context=*/nullptr, /*lookup_context=*/nullptr,
3201
3205
  /*prefetch_buffer=*/nullptr, /*for_compaction=*/false,
3202
3206
  /*async_read=*/false, tmp_status, /*use_block_cache_for_lookup=*/true));
@@ -3228,7 +3232,8 @@ Status BlockBasedTable::GetKVPairsFromDataBlocks(
3228
3232
  return Status::OK();
3229
3233
  }
3230
3234
 
3231
- Status BlockBasedTable::DumpTable(WritableFile* out_file) {
3235
+ Status BlockBasedTable::DumpTable(WritableFile* out_file,
3236
+ bool show_sequence_number_type) {
3232
3237
  WritableFileStringStreamAdapter out_file_wrapper(out_file);
3233
3238
  std::ostream out_stream(&out_file_wrapper);
3234
3239
  // Output Footer
@@ -3321,15 +3326,15 @@ Status BlockBasedTable::DumpTable(WritableFile* out_file) {
3321
3326
  out_stream << "Range deletions:\n"
3322
3327
  "--------------------------------------\n";
3323
3328
  for (; range_del_iter->Valid(); range_del_iter->Next()) {
3324
- DumpKeyValue(range_del_iter->key(), range_del_iter->value(),
3325
- out_stream);
3329
+ DumpKeyValue(range_del_iter->key(), range_del_iter->value(), out_stream,
3330
+ show_sequence_number_type);
3326
3331
  }
3327
3332
  out_stream << "\n";
3328
3333
  }
3329
3334
  delete range_del_iter;
3330
3335
  }
3331
3336
  // Output Data blocks
3332
- s = DumpDataBlocks(out_stream);
3337
+ s = DumpDataBlocks(out_stream, show_sequence_number_type);
3333
3338
 
3334
3339
  if (!s.ok()) {
3335
3340
  return s;
@@ -3347,9 +3352,9 @@ Status BlockBasedTable::DumpIndexBlock(std::ostream& out_stream) {
3347
3352
  // TODO: plumb Env::IOActivity, Env::IOPriority
3348
3353
  const ReadOptions read_options;
3349
3354
  std::unique_ptr<InternalIteratorBase<IndexValue>> blockhandles_iter(
3350
- NewIndexIterator(read_options, /*need_upper_bound_check=*/false,
3355
+ NewIndexIterator(read_options, /*disable_prefix_seek=*/false,
3351
3356
  /*input_iter=*/nullptr, /*get_context=*/nullptr,
3352
- /*lookup_contex=*/nullptr));
3357
+ /*lookup_context=*/nullptr));
3353
3358
  Status s = blockhandles_iter->status();
3354
3359
  if (!s.ok()) {
3355
3360
  out_stream << "Can not read Index Block \n\n";
@@ -3394,13 +3399,14 @@ Status BlockBasedTable::DumpIndexBlock(std::ostream& out_stream) {
3394
3399
  return Status::OK();
3395
3400
  }
3396
3401
 
3397
- Status BlockBasedTable::DumpDataBlocks(std::ostream& out_stream) {
3402
+ Status BlockBasedTable::DumpDataBlocks(std::ostream& out_stream,
3403
+ bool show_sequence_number_type) {
3398
3404
  // TODO: plumb Env::IOActivity, Env::IOPriority
3399
3405
  const ReadOptions read_options;
3400
3406
  std::unique_ptr<InternalIteratorBase<IndexValue>> blockhandles_iter(
3401
- NewIndexIterator(read_options, /*need_upper_bound_check=*/false,
3407
+ NewIndexIterator(read_options, /*disable_prefix_seek=*/false,
3402
3408
  /*input_iter=*/nullptr, /*get_context=*/nullptr,
3403
- /*lookup_contex=*/nullptr));
3409
+ /*lookup_context=*/nullptr));
3404
3410
  Status s = blockhandles_iter->status();
3405
3411
  if (!s.ok()) {
3406
3412
  out_stream << "Can not read Index Block \n\n";
@@ -3433,7 +3439,7 @@ Status BlockBasedTable::DumpDataBlocks(std::ostream& out_stream) {
3433
3439
  Status tmp_status;
3434
3440
  datablock_iter.reset(NewDataBlockIterator<DataBlockIter>(
3435
3441
  read_options, blockhandles_iter->value().handle,
3436
- /*input_iter=*/nullptr, /*type=*/BlockType::kData,
3442
+ /*input_iter=*/nullptr, /*block_type=*/BlockType::kData,
3437
3443
  /*get_context=*/nullptr, /*lookup_context=*/nullptr,
3438
3444
  /*prefetch_buffer=*/nullptr, /*for_compaction=*/false,
3439
3445
  /*async_read=*/false, tmp_status, /*use_block_cache_for_lookup=*/true));
@@ -3451,7 +3457,8 @@ Status BlockBasedTable::DumpDataBlocks(std::ostream& out_stream) {
3451
3457
  out_stream << "Error reading the block - Skipped \n";
3452
3458
  break;
3453
3459
  }
3454
- DumpKeyValue(datablock_iter->key(), datablock_iter->value(), out_stream);
3460
+ DumpKeyValue(datablock_iter->key(), datablock_iter->value(), out_stream,
3461
+ show_sequence_number_type);
3455
3462
  }
3456
3463
  out_stream << "\n";
3457
3464
  }
@@ -3473,14 +3480,26 @@ Status BlockBasedTable::DumpDataBlocks(std::ostream& out_stream) {
3473
3480
  }
3474
3481
 
3475
3482
  void BlockBasedTable::DumpKeyValue(const Slice& key, const Slice& value,
3476
- std::ostream& out_stream) {
3477
- InternalKey ikey;
3478
- ikey.DecodeFrom(key);
3483
+ std::ostream& out_stream,
3484
+ bool show_sequence_number_type) {
3485
+ ParsedInternalKey result;
3486
+ auto s = ParseInternalKey(key, &result, true);
3487
+ if (!s.ok()) {
3488
+ out_stream << "Error parsing internal key - Skipped \n";
3489
+ return;
3490
+ }
3479
3491
 
3480
- out_stream << " HEX " << ikey.user_key().ToString(true) << ": "
3481
- << value.ToString(true) << "\n";
3492
+ if (show_sequence_number_type) {
3493
+ out_stream << " HEX " << result.user_key.ToString(true)
3494
+ << " seq: " << result.sequence
3495
+ << " type: " << std::to_string(result.type) << " : "
3496
+ << value.ToString(true) << "\n";
3497
+ } else {
3498
+ out_stream << " HEX " << result.user_key.ToString(true) << ": "
3499
+ << value.ToString(true) << "\n";
3500
+ }
3482
3501
 
3483
- std::string str_key = ikey.user_key().ToString();
3502
+ std::string str_key = result.user_key.ToString();
3484
3503
  std::string str_value = value.ToString();
3485
3504
  std::string res_key, res_value;
3486
3505
  char cspace = ' ';