@nxtedition/rocksdb 13.5.12 → 14.0.0

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 (232) hide show
  1. package/binding.cc +33 -2
  2. package/binding.gyp +2 -2
  3. package/chained-batch.js +9 -16
  4. package/deps/rocksdb/rocksdb/BUCK +18 -1
  5. package/deps/rocksdb/rocksdb/CMakeLists.txt +10 -3
  6. package/deps/rocksdb/rocksdb/Makefile +20 -9
  7. package/deps/rocksdb/rocksdb/cache/cache_bench_tool.cc +90 -13
  8. package/deps/rocksdb/rocksdb/cache/clock_cache.cc +88 -75
  9. package/deps/rocksdb/rocksdb/cache/clock_cache.h +44 -36
  10. package/deps/rocksdb/rocksdb/cache/compressed_secondary_cache.cc +184 -148
  11. package/deps/rocksdb/rocksdb/cache/compressed_secondary_cache.h +5 -11
  12. package/deps/rocksdb/rocksdb/cache/compressed_secondary_cache_test.cc +116 -47
  13. package/deps/rocksdb/rocksdb/cache/lru_cache_test.cc +1 -1
  14. package/deps/rocksdb/rocksdb/cache/secondary_cache_adapter.cc +3 -6
  15. package/deps/rocksdb/rocksdb/db/arena_wrapped_db_iter.h +1 -1
  16. package/deps/rocksdb/rocksdb/db/builder.cc +4 -2
  17. package/deps/rocksdb/rocksdb/db/c.cc +207 -0
  18. package/deps/rocksdb/rocksdb/db/c_test.c +72 -0
  19. package/deps/rocksdb/rocksdb/db/column_family.cc +3 -2
  20. package/deps/rocksdb/rocksdb/db/column_family.h +5 -0
  21. package/deps/rocksdb/rocksdb/db/compact_files_test.cc +4 -0
  22. package/deps/rocksdb/rocksdb/db/compaction/compaction.cc +2 -0
  23. package/deps/rocksdb/rocksdb/db/compaction/compaction_iterator.cc +51 -38
  24. package/deps/rocksdb/rocksdb/db/compaction/compaction_iterator.h +29 -12
  25. package/deps/rocksdb/rocksdb/db/compaction/compaction_iterator_test.cc +5 -10
  26. package/deps/rocksdb/rocksdb/db/compaction/compaction_job.cc +566 -366
  27. package/deps/rocksdb/rocksdb/db/compaction/compaction_job.h +131 -4
  28. package/deps/rocksdb/rocksdb/db/compaction/compaction_outputs.cc +1 -0
  29. package/deps/rocksdb/rocksdb/db/compaction/compaction_outputs.h +7 -0
  30. package/deps/rocksdb/rocksdb/db/compaction/compaction_picker.cc +4 -4
  31. package/deps/rocksdb/rocksdb/db/compaction/compaction_picker.h +13 -14
  32. package/deps/rocksdb/rocksdb/db/compaction/compaction_picker_fifo.cc +12 -7
  33. package/deps/rocksdb/rocksdb/db/compaction/compaction_picker_fifo.h +8 -10
  34. package/deps/rocksdb/rocksdb/db/compaction/compaction_picker_test.cc +97 -76
  35. package/deps/rocksdb/rocksdb/db/compaction/compaction_picker_universal.cc +11 -14
  36. package/deps/rocksdb/rocksdb/db/compaction/compaction_service_job.cc +1 -1
  37. package/deps/rocksdb/rocksdb/db/compaction/subcompaction_state.h +8 -0
  38. package/deps/rocksdb/rocksdb/db/compaction/tiered_compaction_test.cc +16 -3
  39. package/deps/rocksdb/rocksdb/db/db_basic_test.cc +1 -0
  40. package/deps/rocksdb/rocksdb/db/db_compaction_test.cc +448 -1
  41. package/deps/rocksdb/rocksdb/db/db_impl/db_impl.cc +22 -20
  42. package/deps/rocksdb/rocksdb/db/db_impl/db_impl.h +4 -1
  43. package/deps/rocksdb/rocksdb/db/db_impl/db_impl_compaction_flush.cc +5 -5
  44. package/deps/rocksdb/rocksdb/db/db_impl/db_impl_open.cc +7 -3
  45. package/deps/rocksdb/rocksdb/db/db_impl/db_impl_secondary.cc +1 -1
  46. package/deps/rocksdb/rocksdb/db/db_iter.cc +104 -0
  47. package/deps/rocksdb/rocksdb/db/db_iter.h +4 -11
  48. package/deps/rocksdb/rocksdb/db/db_iterator_test.cc +331 -58
  49. package/deps/rocksdb/rocksdb/db/db_memtable_test.cc +129 -0
  50. package/deps/rocksdb/rocksdb/db/db_sst_test.cc +64 -0
  51. package/deps/rocksdb/rocksdb/db/db_table_properties_test.cc +40 -0
  52. package/deps/rocksdb/rocksdb/db/db_test2.cc +25 -15
  53. package/deps/rocksdb/rocksdb/db/db_test_util.cc +42 -24
  54. package/deps/rocksdb/rocksdb/db/db_test_util.h +29 -14
  55. package/deps/rocksdb/rocksdb/db/db_universal_compaction_test.cc +69 -36
  56. package/deps/rocksdb/rocksdb/db/db_with_timestamp_basic_test.cc +0 -1
  57. package/deps/rocksdb/rocksdb/db/event_helpers.cc +1 -0
  58. package/deps/rocksdb/rocksdb/db/experimental.cc +5 -4
  59. package/deps/rocksdb/rocksdb/db/external_sst_file_basic_test.cc +8 -1
  60. package/deps/rocksdb/rocksdb/db/external_sst_file_ingestion_job.cc +275 -79
  61. package/deps/rocksdb/rocksdb/db/external_sst_file_ingestion_job.h +23 -5
  62. package/deps/rocksdb/rocksdb/db/external_sst_file_test.cc +591 -175
  63. package/deps/rocksdb/rocksdb/db/flush_job.cc +3 -4
  64. package/deps/rocksdb/rocksdb/db/log_reader.cc +5 -2
  65. package/deps/rocksdb/rocksdb/db/memtable.cc +84 -35
  66. package/deps/rocksdb/rocksdb/db/memtable.h +39 -34
  67. package/deps/rocksdb/rocksdb/db/merge_helper.cc +1 -0
  68. package/deps/rocksdb/rocksdb/db/merge_operator.cc +1 -1
  69. package/deps/rocksdb/rocksdb/db/multi_scan.cc +11 -5
  70. package/deps/rocksdb/rocksdb/db/version_edit.cc +1 -1
  71. package/deps/rocksdb/rocksdb/db/version_edit.h +1 -1
  72. package/deps/rocksdb/rocksdb/db/version_edit_handler.cc +34 -14
  73. package/deps/rocksdb/rocksdb/db/version_edit_handler.h +28 -5
  74. package/deps/rocksdb/rocksdb/db/version_set.cc +159 -14
  75. package/deps/rocksdb/rocksdb/db/version_set.h +2 -0
  76. package/deps/rocksdb/rocksdb/db_stress_tool/CMakeLists.txt +1 -1
  77. package/deps/rocksdb/rocksdb/db_stress_tool/db_stress_common.cc +60 -0
  78. package/deps/rocksdb/rocksdb/db_stress_tool/db_stress_common.h +16 -1
  79. package/deps/rocksdb/rocksdb/db_stress_tool/db_stress_compaction_service.h +75 -10
  80. package/deps/rocksdb/rocksdb/db_stress_tool/db_stress_compression_manager.cc +28 -0
  81. package/deps/rocksdb/rocksdb/db_stress_tool/db_stress_compression_manager.h +2 -0
  82. package/deps/rocksdb/rocksdb/db_stress_tool/db_stress_driver.cc +31 -1
  83. package/deps/rocksdb/rocksdb/db_stress_tool/db_stress_gflags.cc +50 -2
  84. package/deps/rocksdb/rocksdb/db_stress_tool/db_stress_shared_state.h +57 -0
  85. package/deps/rocksdb/rocksdb/db_stress_tool/db_stress_stat.h +0 -4
  86. package/deps/rocksdb/rocksdb/db_stress_tool/db_stress_test_base.cc +266 -35
  87. package/deps/rocksdb/rocksdb/db_stress_tool/db_stress_test_base.h +5 -0
  88. package/deps/rocksdb/rocksdb/db_stress_tool/db_stress_tool.cc +0 -6
  89. package/deps/rocksdb/rocksdb/db_stress_tool/no_batched_ops_stress.cc +18 -2
  90. package/deps/rocksdb/rocksdb/env/env.cc +12 -0
  91. package/deps/rocksdb/rocksdb/env/env_test.cc +18 -0
  92. package/deps/rocksdb/rocksdb/env/file_system_tracer.cc +2 -0
  93. package/deps/rocksdb/rocksdb/env/fs_posix.cc +9 -5
  94. package/deps/rocksdb/rocksdb/env/io_posix.cc +4 -2
  95. package/deps/rocksdb/rocksdb/file/random_access_file_reader.cc +19 -0
  96. package/deps/rocksdb/rocksdb/include/rocksdb/advanced_compression.h +33 -31
  97. package/deps/rocksdb/rocksdb/include/rocksdb/advanced_options.h +42 -9
  98. package/deps/rocksdb/rocksdb/include/rocksdb/c.h +93 -0
  99. package/deps/rocksdb/rocksdb/include/rocksdb/cache.h +43 -49
  100. package/deps/rocksdb/rocksdb/include/rocksdb/compaction_job_stats.h +4 -3
  101. package/deps/rocksdb/rocksdb/include/rocksdb/compression_type.h +8 -6
  102. package/deps/rocksdb/rocksdb/include/rocksdb/data_structure.h +487 -0
  103. package/deps/rocksdb/rocksdb/include/rocksdb/db.h +11 -12
  104. package/deps/rocksdb/rocksdb/include/rocksdb/env.h +135 -1
  105. package/deps/rocksdb/rocksdb/include/rocksdb/file_system.h +5 -0
  106. package/deps/rocksdb/rocksdb/include/rocksdb/iostats_context.h +12 -0
  107. package/deps/rocksdb/rocksdb/include/rocksdb/iterator.h +1 -1
  108. package/deps/rocksdb/rocksdb/include/rocksdb/ldb_tool.h +8 -0
  109. package/deps/rocksdb/rocksdb/include/rocksdb/memtablerep.h +12 -8
  110. package/deps/rocksdb/rocksdb/include/rocksdb/metadata.h +3 -0
  111. package/deps/rocksdb/rocksdb/include/rocksdb/multi_scan.h +19 -9
  112. package/deps/rocksdb/rocksdb/include/rocksdb/options.h +219 -24
  113. package/deps/rocksdb/rocksdb/include/rocksdb/point_lock_bench_tool.h +14 -0
  114. package/deps/rocksdb/rocksdb/include/rocksdb/secondary_cache.h +2 -2
  115. package/deps/rocksdb/rocksdb/include/rocksdb/slice.h +1 -1
  116. package/deps/rocksdb/rocksdb/include/rocksdb/statistics.h +7 -0
  117. package/deps/rocksdb/rocksdb/include/rocksdb/status.h +16 -0
  118. package/deps/rocksdb/rocksdb/include/rocksdb/table.h +16 -4
  119. package/deps/rocksdb/rocksdb/include/rocksdb/table_properties.h +13 -0
  120. package/deps/rocksdb/rocksdb/include/rocksdb/types.h +4 -0
  121. package/deps/rocksdb/rocksdb/include/rocksdb/universal_compaction.h +0 -2
  122. package/deps/rocksdb/rocksdb/include/rocksdb/user_defined_index.h +45 -0
  123. package/deps/rocksdb/rocksdb/include/rocksdb/utilities/cache_dump_load.h +1 -1
  124. package/deps/rocksdb/rocksdb/include/rocksdb/utilities/stackable_db.h +1 -1
  125. package/deps/rocksdb/rocksdb/include/rocksdb/utilities/transaction.h +6 -1
  126. package/deps/rocksdb/rocksdb/include/rocksdb/utilities/transaction_db.h +21 -0
  127. package/deps/rocksdb/rocksdb/include/rocksdb/version.h +2 -2
  128. package/deps/rocksdb/rocksdb/memory/memory_allocator_impl.h +3 -3
  129. package/deps/rocksdb/rocksdb/memtable/inlineskiplist.h +77 -51
  130. package/deps/rocksdb/rocksdb/memtable/skiplist.h +10 -13
  131. package/deps/rocksdb/rocksdb/memtable/skiplistrep.cc +16 -7
  132. package/deps/rocksdb/rocksdb/memtable/vectorrep.cc +9 -4
  133. package/deps/rocksdb/rocksdb/monitoring/iostats_context.cc +2 -0
  134. package/deps/rocksdb/rocksdb/monitoring/statistics.cc +6 -0
  135. package/deps/rocksdb/rocksdb/options/cf_options.cc +13 -1
  136. package/deps/rocksdb/rocksdb/options/cf_options.h +6 -2
  137. package/deps/rocksdb/rocksdb/options/options.cc +2 -0
  138. package/deps/rocksdb/rocksdb/options/options_helper.cc +9 -8
  139. package/deps/rocksdb/rocksdb/options/options_settable_test.cc +9 -5
  140. package/deps/rocksdb/rocksdb/port/mmap.cc +1 -1
  141. package/deps/rocksdb/rocksdb/port/win/xpress_win.cc +51 -0
  142. package/deps/rocksdb/rocksdb/port/win/xpress_win.h +4 -0
  143. package/deps/rocksdb/rocksdb/src.mk +8 -2
  144. package/deps/rocksdb/rocksdb/table/block_based/block_based_table_builder.cc +1125 -765
  145. package/deps/rocksdb/rocksdb/table/block_based/block_based_table_builder.h +35 -24
  146. package/deps/rocksdb/rocksdb/table/block_based/block_based_table_factory.cc +29 -4
  147. package/deps/rocksdb/rocksdb/table/block_based/block_based_table_iterator.cc +732 -256
  148. package/deps/rocksdb/rocksdb/table/block_based/block_based_table_iterator.h +225 -16
  149. package/deps/rocksdb/rocksdb/table/block_based/block_based_table_reader.cc +102 -26
  150. package/deps/rocksdb/rocksdb/table/block_based/block_based_table_reader.h +1 -1
  151. package/deps/rocksdb/rocksdb/table/block_based/block_based_table_reader_sync_and_async.h +2 -75
  152. package/deps/rocksdb/rocksdb/table/block_based/block_based_table_reader_test.cc +433 -141
  153. package/deps/rocksdb/rocksdb/table/block_based/block_builder.h +2 -0
  154. package/deps/rocksdb/rocksdb/table/block_based/flush_block_policy.cc +17 -10
  155. package/deps/rocksdb/rocksdb/table/block_based/flush_block_policy_impl.h +20 -0
  156. package/deps/rocksdb/rocksdb/table/block_based/index_builder.cc +112 -85
  157. package/deps/rocksdb/rocksdb/table/block_based/index_builder.h +191 -36
  158. package/deps/rocksdb/rocksdb/table/block_based/partitioned_filter_block.cc +2 -2
  159. package/deps/rocksdb/rocksdb/table/block_based/partitioned_filter_block_test.cc +1 -1
  160. package/deps/rocksdb/rocksdb/table/block_based/user_defined_index_wrapper.h +108 -31
  161. package/deps/rocksdb/rocksdb/table/external_table.cc +7 -3
  162. package/deps/rocksdb/rocksdb/table/format.cc +6 -12
  163. package/deps/rocksdb/rocksdb/table/format.h +10 -0
  164. package/deps/rocksdb/rocksdb/table/internal_iterator.h +1 -1
  165. package/deps/rocksdb/rocksdb/table/iterator_wrapper.h +1 -1
  166. package/deps/rocksdb/rocksdb/table/merging_iterator.cc +1 -1
  167. package/deps/rocksdb/rocksdb/table/meta_blocks.cc +5 -0
  168. package/deps/rocksdb/rocksdb/table/multiget_context.h +3 -1
  169. package/deps/rocksdb/rocksdb/table/sst_file_dumper.cc +118 -46
  170. package/deps/rocksdb/rocksdb/table/sst_file_dumper.h +9 -8
  171. package/deps/rocksdb/rocksdb/table/table_builder.h +5 -0
  172. package/deps/rocksdb/rocksdb/table/table_properties.cc +16 -0
  173. package/deps/rocksdb/rocksdb/table/table_test.cc +1540 -155
  174. package/deps/rocksdb/rocksdb/test_util/testutil.h +21 -5
  175. package/deps/rocksdb/rocksdb/tools/db_bench_tool.cc +26 -5
  176. package/deps/rocksdb/rocksdb/tools/ldb.cc +1 -2
  177. package/deps/rocksdb/rocksdb/tools/ldb_cmd.cc +2 -0
  178. package/deps/rocksdb/rocksdb/tools/ldb_tool.cc +9 -3
  179. package/deps/rocksdb/rocksdb/tools/sst_dump_test.cc +133 -165
  180. package/deps/rocksdb/rocksdb/tools/sst_dump_tool.cc +173 -64
  181. package/deps/rocksdb/rocksdb/util/aligned_buffer.h +69 -0
  182. package/deps/rocksdb/rocksdb/util/atomic.h +6 -0
  183. package/deps/rocksdb/rocksdb/util/auto_tune_compressor.cc +29 -20
  184. package/deps/rocksdb/rocksdb/util/auto_tune_compressor.h +10 -6
  185. package/deps/rocksdb/rocksdb/util/bit_fields.h +338 -0
  186. package/deps/rocksdb/rocksdb/util/coding.h +3 -3
  187. package/deps/rocksdb/rocksdb/util/compaction_job_stats_impl.cc +2 -2
  188. package/deps/rocksdb/rocksdb/util/compression.cc +777 -82
  189. package/deps/rocksdb/rocksdb/util/compression.h +5 -0
  190. package/deps/rocksdb/rocksdb/util/compression_test.cc +5 -3
  191. package/deps/rocksdb/rocksdb/util/dynamic_bloom.cc +2 -2
  192. package/deps/rocksdb/rocksdb/util/dynamic_bloom.h +15 -14
  193. package/deps/rocksdb/rocksdb/util/interval_test.cc +102 -0
  194. package/deps/rocksdb/rocksdb/util/semaphore.h +164 -0
  195. package/deps/rocksdb/rocksdb/util/simple_mixed_compressor.cc +10 -6
  196. package/deps/rocksdb/rocksdb/util/simple_mixed_compressor.h +4 -2
  197. package/deps/rocksdb/rocksdb/util/slice_test.cc +136 -0
  198. package/deps/rocksdb/rocksdb/util/status.cc +1 -0
  199. package/deps/rocksdb/rocksdb/util/string_util.cc +2 -16
  200. package/deps/rocksdb/rocksdb/utilities/cache_dump_load_impl.cc +1 -1
  201. package/deps/rocksdb/rocksdb/utilities/cache_dump_load_impl.h +1 -1
  202. package/deps/rocksdb/rocksdb/utilities/fault_injection_fs.cc +7 -4
  203. package/deps/rocksdb/rocksdb/utilities/fault_injection_fs.h +35 -14
  204. package/deps/rocksdb/rocksdb/utilities/persistent_cache/hash_table_test.cc +2 -0
  205. package/deps/rocksdb/rocksdb/utilities/transactions/lock/lock_manager.cc +5 -2
  206. package/deps/rocksdb/rocksdb/utilities/transactions/lock/point/any_lock_manager_test.h +244 -0
  207. package/deps/rocksdb/rocksdb/utilities/transactions/lock/point/point_lock_bench.cc +18 -0
  208. package/deps/rocksdb/rocksdb/utilities/transactions/lock/point/point_lock_bench_tool.cc +159 -0
  209. package/deps/rocksdb/rocksdb/utilities/transactions/lock/point/point_lock_manager.cc +1244 -161
  210. package/deps/rocksdb/rocksdb/utilities/transactions/lock/point/point_lock_manager.h +66 -12
  211. package/deps/rocksdb/rocksdb/utilities/transactions/lock/point/point_lock_manager_stress_test.cc +103 -0
  212. package/deps/rocksdb/rocksdb/utilities/transactions/lock/point/point_lock_manager_test.cc +1275 -8
  213. package/deps/rocksdb/rocksdb/utilities/transactions/lock/point/point_lock_manager_test.h +40 -262
  214. package/deps/rocksdb/rocksdb/utilities/transactions/lock/point/point_lock_manager_test_common.h +78 -0
  215. package/deps/rocksdb/rocksdb/utilities/transactions/lock/point/point_lock_validation_test_runner.h +469 -0
  216. package/deps/rocksdb/rocksdb/utilities/transactions/lock/range/range_locking_test.cc +2 -6
  217. package/deps/rocksdb/rocksdb/utilities/transactions/pessimistic_transaction.cc +4 -0
  218. package/deps/rocksdb/rocksdb/utilities/transactions/pessimistic_transaction.h +9 -1
  219. package/deps/rocksdb/rocksdb/utilities/transactions/timestamped_snapshot_test.cc +18 -9
  220. package/deps/rocksdb/rocksdb/utilities/transactions/transaction_base.h +2 -0
  221. package/deps/rocksdb/rocksdb/utilities/transactions/transaction_db_mutex_impl.cc +2 -1
  222. package/deps/rocksdb/rocksdb/utilities/transactions/transaction_test.cc +72 -44
  223. package/deps/rocksdb/rocksdb/utilities/transactions/transaction_test.h +92 -15
  224. package/deps/rocksdb/rocksdb/utilities/transactions/write_committed_transaction_ts_test.cc +6 -20
  225. package/deps/rocksdb/rocksdb/utilities/transactions/write_prepared_transaction_test.cc +143 -112
  226. package/deps/rocksdb/rocksdb/utilities/transactions/write_unprepared_transaction_test.cc +23 -16
  227. package/index.js +3 -3
  228. package/package.json +1 -1
  229. package/prebuilds/darwin-arm64/@nxtedition+rocksdb.node +0 -0
  230. package/prebuilds/linux-x64/@nxtedition+rocksdb.node +0 -0
  231. package/util.h +38 -12
  232. package/deps/rocksdb/rocksdb/db_stress_tool/db_stress_stat.cc +0 -17
@@ -53,6 +53,7 @@
53
53
  #include "rocksdb/trace_record.h"
54
54
  #include "rocksdb/unique_id.h"
55
55
  #include "rocksdb/user_defined_index.h"
56
+ #include "rocksdb/utilities/object_registry.h"
56
57
  #include "rocksdb/write_buffer_manager.h"
57
58
  #include "table/block_based/block.h"
58
59
  #include "table/block_based/block_based_table_builder.h"
@@ -2059,7 +2060,7 @@ TEST_P(BlockBasedTableTest, PrefetchTest) {
2059
2060
 
2060
2061
  // Simple
2061
2062
  PrefetchRange(&c, &opt, &table_options,
2062
- /*key_range=*/"k01", "k05",
2063
+ /*key_begin=*/"k01", /*key_end=*/"k05",
2063
2064
  /*keys_in_cache=*/{"k01", "k02", "k03", "k04", "k05"},
2064
2065
  /*keys_not_in_cache=*/{"k06", "k07"});
2065
2066
  PrefetchRange(&c, &opt, &table_options, "k01", "k01", {"k01", "k02", "k03"},
@@ -4716,8 +4717,8 @@ TEST_F(GeneralTableTest, ApproximateOffsetOfPlain) {
4716
4717
  // an arbitrary slice between k04 and k05, either before or after k04a
4717
4718
  ASSERT_TRUE(Between(c.ApproximateOffsetOf("k04a"), 10000, 211000));
4718
4719
  ASSERT_TRUE(Between(c.ApproximateOffsetOf("k05"), 210000, 211000));
4719
- ASSERT_TRUE(Between(c.ApproximateOffsetOf("k06"), 510000, 511000));
4720
- ASSERT_TRUE(Between(c.ApproximateOffsetOf("k07"), 510000, 511000));
4720
+ ASSERT_TRUE(Between(c.ApproximateOffsetOf("k06"), 510000, 512000));
4721
+ ASSERT_TRUE(Between(c.ApproximateOffsetOf("k07"), 510000, 512000));
4721
4722
  ASSERT_TRUE(Between(c.ApproximateOffsetOf("xyz"), 610000, 612000));
4722
4723
  c.ResetTableReader();
4723
4724
  }
@@ -6230,6 +6231,12 @@ TEST_P(BlockBasedTableTest, OutOfBoundOnNext) {
6230
6231
  class ChargeCompressionDictionaryBuildingBufferTest
6231
6232
  : public BlockBasedTableTestBase {};
6232
6233
  TEST_F(ChargeCompressionDictionaryBuildingBufferTest, Basic) {
6234
+ if (GetSupportedDictCompressions().empty()) {
6235
+ ROCKSDB_GTEST_SKIP("No supported dict compression");
6236
+ return;
6237
+ }
6238
+ const auto kCompression = GetSupportedDictCompressions()[0];
6239
+
6233
6240
  constexpr std::size_t kSizeDummyEntry = 256 * 1024;
6234
6241
  constexpr std::size_t kMetaDataChargeOverhead = 10000;
6235
6242
  constexpr std::size_t kCacheCapacity = 8 * 1024 * 1024;
@@ -6253,7 +6260,7 @@ TEST_F(ChargeCompressionDictionaryBuildingBufferTest, Basic) {
6253
6260
  {CacheEntryRole::kCompressionDictionaryBuildingBuffer,
6254
6261
  {/*.charged = */ charge_compression_dictionary_building_buffer}});
6255
6262
  Options options;
6256
- options.compression = kSnappyCompression;
6263
+ options.compression = kCompression;
6257
6264
  options.compression_opts.max_dict_bytes = kMaxDictBytes;
6258
6265
  options.compression_opts.max_dict_buffer_bytes = kMaxDictBufferBytes;
6259
6266
  options.table_factory.reset(NewBlockBasedTableFactory(table_options));
@@ -6274,7 +6281,7 @@ TEST_F(ChargeCompressionDictionaryBuildingBufferTest, Basic) {
6274
6281
  options.table_factory->NewTableBuilder(
6275
6282
  TableBuilderOptions(ioptions, moptions, read_options, write_options,
6276
6283
  ikc, &internal_tbl_prop_coll_factories,
6277
- kSnappyCompression, options.compression_opts,
6284
+ kCompression, options.compression_opts,
6278
6285
  kUnknownColumnFamily, "test_cf", -1 /* level */,
6279
6286
  kUnknownNewestKeyTime),
6280
6287
  file_writer.get()));
@@ -6313,6 +6320,12 @@ TEST_F(ChargeCompressionDictionaryBuildingBufferTest, Basic) {
6313
6320
 
6314
6321
  TEST_F(ChargeCompressionDictionaryBuildingBufferTest,
6315
6322
  BasicWithBufferLimitExceed) {
6323
+ if (GetSupportedDictCompressions().empty()) {
6324
+ ROCKSDB_GTEST_SKIP("No supported dict compression");
6325
+ return;
6326
+ }
6327
+ const auto kCompression = GetSupportedDictCompressions()[0];
6328
+
6316
6329
  constexpr std::size_t kSizeDummyEntry = 256 * 1024;
6317
6330
  constexpr std::size_t kMetaDataChargeOverhead = 10000;
6318
6331
  constexpr std::size_t kCacheCapacity = 8 * 1024 * 1024;
@@ -6332,7 +6345,7 @@ TEST_F(ChargeCompressionDictionaryBuildingBufferTest,
6332
6345
  std::make_shared<FlushBlockEveryKeyPolicyFactory>();
6333
6346
 
6334
6347
  Options options;
6335
- options.compression = kSnappyCompression;
6348
+ options.compression = kCompression;
6336
6349
  options.compression_opts.max_dict_bytes = kMaxDictBytes;
6337
6350
  options.compression_opts.max_dict_buffer_bytes = kMaxDictBufferBytes;
6338
6351
  options.table_factory.reset(NewBlockBasedTableFactory(table_options));
@@ -6351,7 +6364,7 @@ TEST_F(ChargeCompressionDictionaryBuildingBufferTest,
6351
6364
  const WriteOptions write_options;
6352
6365
  std::unique_ptr<TableBuilder> builder(options.table_factory->NewTableBuilder(
6353
6366
  TableBuilderOptions(ioptions, moptions, read_options, write_options, ikc,
6354
- &internal_tbl_prop_coll_factories, kSnappyCompression,
6367
+ &internal_tbl_prop_coll_factories, kCompression,
6355
6368
  options.compression_opts, kUnknownColumnFamily,
6356
6369
  "test_cf", -1 /* level */, kUnknownNewestKeyTime),
6357
6370
  file_writer.get()));
@@ -6394,6 +6407,12 @@ TEST_F(ChargeCompressionDictionaryBuildingBufferTest,
6394
6407
  }
6395
6408
 
6396
6409
  TEST_F(ChargeCompressionDictionaryBuildingBufferTest, BasicWithCacheFull) {
6410
+ if (GetSupportedDictCompressions().empty()) {
6411
+ ROCKSDB_GTEST_SKIP("No supported dict compression");
6412
+ return;
6413
+ }
6414
+ const auto kCompression = GetSupportedDictCompressions()[0];
6415
+
6397
6416
  constexpr std::size_t kSizeDummyEntry = 256 * 1024;
6398
6417
  constexpr std::size_t kMetaDataChargeOverhead = 10000;
6399
6418
  // A small kCacheCapacity is chosen so that increase cache charging for
@@ -6419,7 +6438,7 @@ TEST_F(ChargeCompressionDictionaryBuildingBufferTest, BasicWithCacheFull) {
6419
6438
  std::make_shared<FlushBlockEveryKeyPolicyFactory>();
6420
6439
 
6421
6440
  Options options;
6422
- options.compression = kSnappyCompression;
6441
+ options.compression = kCompression;
6423
6442
  options.compression_opts.max_dict_bytes = kMaxDictBytes;
6424
6443
  options.compression_opts.max_dict_buffer_bytes = kMaxDictBufferBytes;
6425
6444
  options.table_factory.reset(NewBlockBasedTableFactory(table_options));
@@ -6438,7 +6457,7 @@ TEST_F(ChargeCompressionDictionaryBuildingBufferTest, BasicWithCacheFull) {
6438
6457
  const WriteOptions write_options;
6439
6458
  std::unique_ptr<TableBuilder> builder(options.table_factory->NewTableBuilder(
6440
6459
  TableBuilderOptions(ioptions, moptions, read_options, write_options, ikc,
6441
- &internal_tbl_prop_coll_factories, kSnappyCompression,
6460
+ &internal_tbl_prop_coll_factories, kCompression,
6442
6461
  options.compression_opts, kUnknownColumnFamily,
6443
6462
  "test_cf", -1 /* level */, kUnknownNewestKeyTime),
6444
6463
  file_writer.get()));
@@ -7180,9 +7199,9 @@ TEST_F(ExternalTableTest, DBMultiScanTest) {
7180
7199
 
7181
7200
  std::vector<std::string> key_ranges({"k03", "k10", "k25", "k50"});
7182
7201
  ReadOptions ro;
7183
- std::vector<ScanOptions> scan_options(
7184
- {ScanOptions(key_ranges[0], key_ranges[1]),
7185
- ScanOptions(key_ranges[2], key_ranges[3])});
7202
+ MultiScanArgs scan_options(BytewiseComparator());
7203
+ scan_options.insert(key_ranges[0], key_ranges[1]);
7204
+ scan_options.insert(key_ranges[2], key_ranges[3]);
7186
7205
  std::unique_ptr<MultiScan> iter = db->NewMultiScan(ro, cfh, scan_options);
7187
7206
  try {
7188
7207
  int idx = 0;
@@ -7209,7 +7228,10 @@ TEST_F(ExternalTableTest, DBMultiScanTest) {
7209
7228
 
7210
7229
  // Test the overlapping scan case
7211
7230
  key_ranges[1] = "k30";
7212
- scan_options[0] = ScanOptions(key_ranges[0], key_ranges[1]);
7231
+ scan_options = MultiScanArgs(BytewiseComparator());
7232
+ scan_options.insert(key_ranges[0], key_ranges[1]);
7233
+ scan_options.insert(key_ranges[2], key_ranges[3]);
7234
+
7213
7235
  iter = db->NewMultiScan(ro, cfh, scan_options);
7214
7236
  try {
7215
7237
  int idx = 0;
@@ -7226,8 +7248,6 @@ TEST_F(ExternalTableTest, DBMultiScanTest) {
7226
7248
  } catch (MultiScanException& ex) {
7227
7249
  // Make sure exception contains the status
7228
7250
  ASSERT_NOK(ex.status());
7229
- std::cerr << "Iterator returned status " << ex.what();
7230
- abort();
7231
7251
  } catch (std::logic_error& ex) {
7232
7252
  std::cerr << "Iterator returned logic error " << ex.what();
7233
7253
  abort();
@@ -7235,8 +7255,9 @@ TEST_F(ExternalTableTest, DBMultiScanTest) {
7235
7255
  iter.reset();
7236
7256
 
7237
7257
  // Test the no limit scan case
7238
- scan_options[0] = ScanOptions(key_ranges[0]);
7239
- scan_options[1] = ScanOptions(key_ranges[2]);
7258
+ scan_options = MultiScanArgs(BytewiseComparator());
7259
+ scan_options.insert(key_ranges[0]);
7260
+ scan_options.insert(key_ranges[2]);
7240
7261
  iter = db->NewMultiScan(ro, cfh, scan_options);
7241
7262
  try {
7242
7263
  int idx = 0;
@@ -7255,8 +7276,6 @@ TEST_F(ExternalTableTest, DBMultiScanTest) {
7255
7276
  } catch (MultiScanException& ex) {
7256
7277
  // Make sure exception contains the status
7257
7278
  ASSERT_NOK(ex.status());
7258
- std::cerr << "Iterator returned status " << ex.what();
7259
- abort();
7260
7279
  } catch (std::logic_error& ex) {
7261
7280
  std::cerr << "Iterator returned logic error " << ex.what();
7262
7281
  abort();
@@ -7281,7 +7300,7 @@ TEST_F(ExternalTableTest, DBMultiScanTest) {
7281
7300
  }
7282
7301
  } catch (MultiScanException& ex) {
7283
7302
  // Make sure exception contains the status
7284
- ASSERT_EQ(ex.status(), Status::IOError());
7303
+ ASSERT_NOK(ex.status());
7285
7304
  } catch (std::logic_error& ex) {
7286
7305
  std::cerr << "Iterator returned logic error " << ex.what();
7287
7306
  abort();
@@ -7399,7 +7418,7 @@ TEST_F(ExternalTableTest, IngestionTest) {
7399
7418
  ASSERT_OK(db->Close());
7400
7419
  }
7401
7420
 
7402
- class UserDefinedIndexTest : public BlockBasedTableTestBase {
7421
+ class UserDefinedIndexTestBase : public BlockBasedTableTestBase {
7403
7422
  public:
7404
7423
  class CustomFlushBlockPolicy : public FlushBlockPolicy {
7405
7424
  public:
@@ -7436,13 +7455,36 @@ class UserDefinedIndexTest : public BlockBasedTableTestBase {
7436
7455
  class TestUserDefinedIndexFactory : public UserDefinedIndexFactory {
7437
7456
  public:
7438
7457
  const char* Name() const override { return "test_index"; }
7439
- UserDefinedIndexBuilder* NewBuilder() const override {
7440
- return new TestUserDefinedIndexBuilder();
7458
+ Status NewBuilder(
7459
+ const UserDefinedIndexOption& /*option*/,
7460
+ std::unique_ptr<UserDefinedIndexBuilder>& builder) const override {
7461
+ builder = std::make_unique<TestUserDefinedIndexBuilder>();
7462
+ return Status::OK();
7441
7463
  }
7442
7464
 
7465
+ struct CustomizedMapComparator {
7466
+ CustomizedMapComparator(const Comparator* _comparator)
7467
+ : comparator(_comparator) {}
7468
+ const Comparator* comparator;
7469
+ bool operator()(const std::string& lhs, const std::string& rhs) const {
7470
+ return comparator->Compare(lhs, rhs) < 0;
7471
+ }
7472
+ };
7473
+
7474
+ // Deprecated API
7475
+ UserDefinedIndexBuilder* NewBuilder() const override { return nullptr; }
7476
+
7443
7477
  std::unique_ptr<UserDefinedIndexReader> NewReader(
7444
- Slice& index_block) const override {
7445
- return std::make_unique<TestUserDefinedIndexReader>(index_block, this);
7478
+ Slice& /*index_block*/) const override {
7479
+ return nullptr;
7480
+ }
7481
+
7482
+ Status NewReader(
7483
+ const UserDefinedIndexOption& option, Slice& index_block,
7484
+ std::unique_ptr<UserDefinedIndexReader>& reader) const override {
7485
+ reader = std::make_unique<TestUserDefinedIndexReader>(
7486
+ index_block, option.comparator, this);
7487
+ return Status::OK();
7446
7488
  }
7447
7489
 
7448
7490
  uint64_t seek_error_count_ = 0;
@@ -7457,10 +7499,17 @@ class UserDefinedIndexTest : public BlockBasedTableTestBase {
7457
7499
  const Slice* first_key_in_next_block,
7458
7500
  const BlockHandle& block_handle,
7459
7501
  std::string* separator_scratch) override {
7502
+ if (keys_added_ == 0) {
7503
+ return last_key_in_current_block;
7504
+ }
7505
+ EXPECT_EQ(last_key_in_current_block.size(), 5);
7506
+ if (first_key_in_next_block) {
7507
+ EXPECT_EQ(first_key_in_next_block->size(), 5);
7508
+ }
7460
7509
  // Unused parameters
7461
- (void)first_key_in_next_block;
7462
7510
  (void)separator_scratch;
7463
7511
  entries_added_++;
7512
+ index_data_[last_key_in_current_block.ToString()].clear();
7464
7513
  // Store the block handle for each key
7465
7514
  PutFixed64(&index_data_[last_key_in_current_block.ToString()],
7466
7515
  block_handle.offset);
@@ -7472,13 +7521,25 @@ class UserDefinedIndexTest : public BlockBasedTableTestBase {
7472
7521
  return last_key_in_current_block;
7473
7522
  }
7474
7523
 
7475
- void OnKeyAdded(const Slice& /*key*/, ValueType /*value*/,
7524
+ void OnKeyAdded(const Slice& key, ValueType /*value*/,
7476
7525
  const Slice& /*value*/) override {
7526
+ if (key.starts_with("dummy")) {
7527
+ return;
7528
+ }
7529
+ EXPECT_EQ(key.size(), 5);
7477
7530
  // Track keys added to the index
7478
7531
  keys_added_++;
7532
+ // Add dummy entry
7533
+ PutFixed64(&index_data_[key.ToString()], 0);
7534
+ PutFixed64(&index_data_[key.ToString()], 0);
7535
+ PutFixed32(&index_data_[key.ToString()], 0);
7479
7536
  }
7480
7537
 
7481
7538
  Status Finish(Slice* index_contents) override {
7539
+ if (entries_added_ == 0) {
7540
+ *index_contents = Slice();
7541
+ return Status::OK();
7542
+ }
7482
7543
  // Serialize the index data
7483
7544
  std::string result;
7484
7545
  for (const auto& entry : index_data_) {
@@ -7502,8 +7563,11 @@ class UserDefinedIndexTest : public BlockBasedTableTestBase {
7502
7563
  class TestUserDefinedIndexReader : public UserDefinedIndexReader {
7503
7564
  public:
7504
7565
  explicit TestUserDefinedIndexReader(
7505
- Slice& index_block, const TestUserDefinedIndexFactory* factory)
7506
- : factory_(factory) {
7566
+ Slice& index_block, const Comparator* comparator,
7567
+ const TestUserDefinedIndexFactory* factory)
7568
+ : factory_(factory),
7569
+ comparator_(comparator),
7570
+ index_data_(CustomizedMapComparator(comparator)) {
7507
7571
  Slice block = index_block;
7508
7572
  while (!block.empty()) {
7509
7573
  Slice key;
@@ -7525,9 +7589,9 @@ class UserDefinedIndexTest : public BlockBasedTableTestBase {
7525
7589
  }
7526
7590
 
7527
7591
  std::unique_ptr<UserDefinedIndexIterator> NewIterator(
7528
- const ReadOptions& ro) override {
7529
- return std::make_unique<TestUserDefinedIndexIterator>(ro, index_data_,
7530
- factory_);
7592
+ const ReadOptions& /*ro*/) override {
7593
+ return std::make_unique<TestUserDefinedIndexIterator>(
7594
+ index_data_, factory_, comparator_);
7531
7595
  }
7532
7596
 
7533
7597
  size_t ApproximateMemoryUsage() const override { return 0; }
@@ -7536,19 +7600,19 @@ class UserDefinedIndexTest : public BlockBasedTableTestBase {
7536
7600
  class TestUserDefinedIndexIterator : public UserDefinedIndexIterator {
7537
7601
  public:
7538
7602
  TestUserDefinedIndexIterator(
7539
- const ReadOptions& ro,
7540
7603
  std::map<std::string,
7541
- std::pair<UserDefinedIndexBuilder::BlockHandle, uint32_t>>&
7542
- index,
7543
- const TestUserDefinedIndexFactory* factory)
7544
- : ro_(ro),
7545
- index_(index),
7604
+ std::pair<UserDefinedIndexBuilder::BlockHandle, uint32_t>,
7605
+ CustomizedMapComparator>& index,
7606
+ const TestUserDefinedIndexFactory* factory,
7607
+ const Comparator* comparator)
7608
+ : index_(index),
7546
7609
  iter_(index_.end()),
7547
7610
  scan_opts_(nullptr),
7548
7611
  num_opts_(0),
7549
7612
  target_num_keys_(0),
7550
7613
  seek_error_count_(factory->seek_error_count_),
7551
- next_error_count_(factory->next_error_count_) {}
7614
+ next_error_count_(factory->next_error_count_),
7615
+ comparator_(comparator) {}
7552
7616
 
7553
7617
  Status SeekAndGetResult(const Slice& key,
7554
7618
  IterateResult* result) override {
@@ -7561,23 +7625,24 @@ class UserDefinedIndexTest : public BlockBasedTableTestBase {
7561
7625
  return s;
7562
7626
  }
7563
7627
  if (scan_opts_) {
7564
- if (scan_opts_[scan_idx_].range.start.value().compare(key) == 0) {
7565
- EXPECT_TRUE(scan_opts_[scan_idx_].property_bag.has_value());
7566
- target_num_keys_ = std::stoi(scan_opts_[scan_idx_]
7567
- .property_bag.value()
7568
- .find("count")
7569
- ->second);
7570
- scan_idx_++;
7571
- } else {
7572
- scan_opts_ = nullptr;
7573
- }
7628
+ // Seeks should be in order specified in scan_opts_
7629
+ EXPECT_EQ(comparator_->Compare(
7630
+ scan_opts_[scan_idx_].range.start.value(), key),
7631
+ 0);
7632
+ EXPECT_TRUE(scan_opts_[scan_idx_].property_bag.has_value());
7633
+ target_num_keys_ = std::stoi(scan_opts_[scan_idx_]
7634
+ .property_bag.value()
7635
+ .find("count")
7636
+ ->second);
7637
+ scan_idx_++;
7574
7638
  }
7575
7639
  iter_ = index_.lower_bound(key.ToString());
7576
- if (iter_ != index_.end()) {
7640
+ if ((iter_ != index_.end()) && IsInbound()) {
7641
+ AdvanceToNextIndexEntry();
7577
7642
  result->bound_check_result = IterBoundCheck::kInbound;
7578
7643
  result->key = Slice(iter_->first);
7579
7644
  if (scan_opts_ && target_num_keys_ > 0 &&
7580
- iter_->first.compare(key.ToString()) == 0) {
7645
+ comparator_->Compare(key, iter_->first) == 0) {
7581
7646
  target_num_keys_--;
7582
7647
  }
7583
7648
  } else {
@@ -7596,9 +7661,10 @@ class UserDefinedIndexTest : public BlockBasedTableTestBase {
7596
7661
  if (!s.ok()) {
7597
7662
  return s;
7598
7663
  }
7599
- if (ro_.iterate_upper_bound) {
7600
- if (iter_->first.compare(ro_.iterate_upper_bound->ToString()) >=
7601
- 0) {
7664
+ if (scan_opts_ && scan_opts_[scan_idx_ - 1].range.limit.has_value()) {
7665
+ if (comparator_->Compare(
7666
+ iter_->first,
7667
+ scan_opts_[scan_idx_ - 1].range.limit.value()) >= 0) {
7602
7668
  result->bound_check_result = IterBoundCheck::kOutOfBound;
7603
7669
  result->key = Slice();
7604
7670
  return Status::OK();
@@ -7610,7 +7676,8 @@ class UserDefinedIndexTest : public BlockBasedTableTestBase {
7610
7676
  return Status::OK();
7611
7677
  }
7612
7678
  iter_++;
7613
- if (iter_ != index_.end()) {
7679
+ if ((iter_ != index_.end()) && IsInbound()) {
7680
+ AdvanceToNextIndexEntry();
7614
7681
  result->bound_check_result = IterBoundCheck::kInbound;
7615
7682
  result->key = Slice(iter_->first);
7616
7683
  target_num_keys_ -=
@@ -7623,6 +7690,25 @@ class UserDefinedIndexTest : public BlockBasedTableTestBase {
7623
7690
  return Status::OK();
7624
7691
  }
7625
7692
 
7693
+ void AdvanceToNextIndexEntry() {
7694
+ while (iter_->second.second == 0) {
7695
+ iter_++;
7696
+ }
7697
+ }
7698
+
7699
+ bool IsInbound() {
7700
+ if (!scan_opts_) {
7701
+ return true;
7702
+ }
7703
+ if (scan_opts_[scan_idx_ - 1].range.limit.has_value() &&
7704
+ comparator_->Compare(
7705
+ scan_opts_[scan_idx_ - 1].range.limit.value(),
7706
+ iter_->first) <= 0) {
7707
+ return false;
7708
+ }
7709
+ return true;
7710
+ }
7711
+
7626
7712
  UserDefinedIndexBuilder::BlockHandle value() override {
7627
7713
  UserDefinedIndexBuilder::BlockHandle handle{0, 0};
7628
7714
  handle.offset = iter_->second.first.offset;
@@ -7631,16 +7717,17 @@ class UserDefinedIndexTest : public BlockBasedTableTestBase {
7631
7717
  }
7632
7718
 
7633
7719
  void Prepare(const ScanOptions scan_opts[], size_t num_opts) override {
7720
+ // Prepare should only be called once
7721
+ EXPECT_EQ(scan_opts_, nullptr);
7634
7722
  scan_opts_ = scan_opts;
7635
7723
  num_opts_ = num_opts;
7636
7724
  scan_idx_ = 0;
7637
7725
  }
7638
7726
 
7639
7727
  private:
7640
- const ReadOptions& ro_;
7641
7728
  std::map<std::string,
7642
- std::pair<UserDefinedIndexBuilder::BlockHandle, uint32_t>>&
7643
- index_;
7729
+ std::pair<UserDefinedIndexBuilder::BlockHandle, uint32_t>,
7730
+ CustomizedMapComparator>& index_;
7644
7731
  std::map<std::string, std::pair<UserDefinedIndexBuilder::BlockHandle,
7645
7732
  uint32_t>>::iterator iter_;
7646
7733
  const ScanOptions* scan_opts_;
@@ -7649,18 +7736,137 @@ class UserDefinedIndexTest : public BlockBasedTableTestBase {
7649
7736
  uint32_t target_num_keys_;
7650
7737
  uint64_t seek_error_count_;
7651
7738
  uint64_t next_error_count_;
7739
+ const Comparator* comparator_;
7652
7740
  };
7653
7741
 
7654
7742
  const TestUserDefinedIndexFactory* factory_;
7743
+ const Comparator* comparator_;
7655
7744
  std::map<std::string,
7656
- std::pair<UserDefinedIndexBuilder::BlockHandle, uint32_t>>
7745
+ std::pair<UserDefinedIndexBuilder::BlockHandle, uint32_t>,
7746
+ CustomizedMapComparator>
7657
7747
  index_data_;
7658
7748
  };
7659
7749
  };
7750
+
7751
+ protected:
7752
+ std::vector<std::pair<std::string, std::string>> generateKVWithValue(
7753
+ int key_count, const std::string& value) {
7754
+ std::vector<std::pair<std::string, std::string>> kvs(key_count);
7755
+ for (int i = 0; i < key_count; i++) {
7756
+ std::stringstream ss;
7757
+ ss << std::setw(2) << std::setfill('0') << i;
7758
+ std::string key = "key" + ss.str();
7759
+ kvs[i] = std::make_pair(key, value);
7760
+ }
7761
+ if (is_reverse_comparator_) {
7762
+ std::reverse(kvs.begin(), kvs.end());
7763
+ }
7764
+ return kvs;
7765
+ }
7766
+
7767
+ std::vector<std::pair<std::string, std::string>> generateKVs(
7768
+ int key_count, int value_size = 0) {
7769
+ std::vector<std::pair<std::string, std::string>> kvs(key_count);
7770
+ for (int i = 0; i < key_count; i++) {
7771
+ std::stringstream ss;
7772
+ ss << std::setw(2) << std::setfill('0') << i;
7773
+ std::string key = "key" + ss.str();
7774
+ std::string value;
7775
+ if (value_size != 0) {
7776
+ value = rnd.RandomString(1024);
7777
+ } else {
7778
+ value = "value" + ss.str();
7779
+ }
7780
+ kvs[i] = std::make_pair(key, value);
7781
+ }
7782
+ if (is_reverse_comparator_) {
7783
+ std::reverse(kvs.begin(), kvs.end());
7784
+ }
7785
+ return kvs;
7786
+ }
7787
+
7788
+ void BasicTest(bool use_partitioned_index);
7789
+
7790
+ void ValidateMultiScan(
7791
+ std::vector<std::tuple<std::vector<std::string>, int, int>>
7792
+ scan_opt_validation_arg,
7793
+ std::unordered_map<std::string, std::string> property_bag,
7794
+ const ReadOptions& ro, MultiScanArgs& scan_opts,
7795
+ std::vector<int>& key_counts, std::unique_ptr<DB>& db,
7796
+ ColumnFamilyHandle* cfh) {
7797
+ key_counts.clear();
7798
+ (*scan_opts).clear();
7799
+
7800
+ if (is_reverse_comparator_) {
7801
+ for (auto& scan_opt_validation_range : scan_opt_validation_arg) {
7802
+ // reverse each range
7803
+ std::reverse(std::get<0>(scan_opt_validation_range).begin(),
7804
+ std::get<0>(scan_opt_validation_range).end());
7805
+ }
7806
+ // reverse all the ranges
7807
+ std::reverse(scan_opt_validation_arg.begin(),
7808
+ scan_opt_validation_arg.end());
7809
+ }
7810
+
7811
+ for (auto& scan_opt_validation_range : scan_opt_validation_arg) {
7812
+ scan_opts.insert(std::get<0>(scan_opt_validation_range)[0],
7813
+ std::get<0>(scan_opt_validation_range)[1],
7814
+ std::optional(property_bag));
7815
+ if (is_reverse_comparator_) {
7816
+ key_counts.push_back(std::get<2>(scan_opt_validation_range));
7817
+ } else {
7818
+ key_counts.push_back(std::get<1>(scan_opt_validation_range));
7819
+ }
7820
+ }
7821
+
7822
+ Slice ub;
7823
+ ReadOptions read_opts = ro;
7824
+ int key_count = 0;
7825
+ int index = 0;
7826
+ auto opts = scan_opts.GetScanRanges();
7827
+ read_opts.iterate_upper_bound = &ub;
7828
+ std::unique_ptr<Iterator> iter(db->NewIterator(read_opts, cfh));
7829
+ iter->Prepare(scan_opts);
7830
+ static const bool kVerbose = false;
7831
+ for (auto opt : opts) {
7832
+ ub = opt.range.limit.value();
7833
+ iter->Seek(opt.range.start.value());
7834
+ if (kVerbose) {
7835
+ printf("range start key %s, end key %s\n",
7836
+ opt.range.start.value().ToString().c_str(),
7837
+ opt.range.limit.value().ToString().c_str());
7838
+ }
7839
+ EXPECT_OK(iter->status());
7840
+ while (iter->Valid()) {
7841
+ if (kVerbose) {
7842
+ printf("found key %s\n", iter->key().ToString().c_str());
7843
+ }
7844
+ key_count++;
7845
+ iter->Next();
7846
+ }
7847
+ EXPECT_EQ(key_count, key_counts[index]);
7848
+ key_count = 0;
7849
+ index++;
7850
+ }
7851
+ EXPECT_OK(iter->status());
7852
+ }
7853
+ Options options_;
7854
+ const Comparator* comparator_;
7855
+ bool is_reverse_comparator_;
7856
+ Random rnd{301};
7660
7857
  };
7661
7858
 
7662
- TEST_F(UserDefinedIndexTest, BasicTest) {
7663
- Options options;
7859
+ class UserDefinedIndexTest
7860
+ : public UserDefinedIndexTestBase,
7861
+ public testing::WithParamInterface<const Comparator*> {
7862
+ void SetUp() override {
7863
+ comparator_ = GetParam();
7864
+ options_.comparator = comparator_;
7865
+ is_reverse_comparator_ = comparator_ == ReverseBytewiseComparator();
7866
+ }
7867
+ };
7868
+
7869
+ void UserDefinedIndexTestBase::BasicTest(bool use_partitioned_index) {
7664
7870
  BlockBasedTableOptions table_options;
7665
7871
  std::string dbname = test::PerThreadDBPath("user_defined_index_test");
7666
7872
  std::string ingest_file = dbname + "test.sst";
@@ -7669,31 +7875,31 @@ TEST_F(UserDefinedIndexTest, BasicTest) {
7669
7875
  auto user_defined_index_factory =
7670
7876
  std::make_shared<TestUserDefinedIndexFactory>();
7671
7877
  table_options.user_defined_index_factory = user_defined_index_factory;
7672
-
7878
+ if (use_partitioned_index) {
7879
+ table_options.partition_filters = true;
7880
+ table_options.decouple_partitioned_filters = true;
7881
+ table_options.index_type = BlockBasedTableOptions::kTwoLevelIndexSearch;
7882
+ }
7673
7883
  // Set up custom flush block policy that flushes every 3 keys
7674
7884
  table_options.flush_block_policy_factory =
7675
7885
  std::make_shared<CustomFlushBlockPolicyFactory>();
7676
7886
 
7677
- options.table_factory.reset(NewBlockBasedTableFactory(table_options));
7887
+ options_.table_factory.reset(NewBlockBasedTableFactory(table_options));
7678
7888
 
7679
7889
  std::unique_ptr<SstFileWriter> writer;
7680
- writer.reset(new SstFileWriter(EnvOptions(), options));
7890
+ writer.reset(new SstFileWriter(EnvOptions(), options_));
7681
7891
  ASSERT_OK(writer->Open(ingest_file));
7682
7892
 
7683
- // Add 100 keys instead of just 5
7684
- for (int i = 0; i < 100; i++) {
7685
- std::stringstream ss;
7686
- ss << std::setw(2) << std::setfill('0') << i;
7687
- std::string key = "key" + ss.str();
7688
- std::string value = "value" + ss.str();
7689
- ASSERT_OK(writer->Put(key, value));
7893
+ auto kvs = generateKVs(/*key_count*/ 100);
7894
+ for (const auto& kv : kvs) {
7895
+ ASSERT_OK(writer->Put(kv.first, kv.second));
7690
7896
  }
7691
7897
  ASSERT_OK(writer->Finish());
7692
7898
  writer.reset();
7693
7899
 
7694
- ImmutableOptions ioptions(options);
7695
- MutableCFOptions moptions((ColumnFamilyOptions(options)));
7696
- EnvOptions eoptions(options);
7900
+ ImmutableOptions ioptions(options_);
7901
+ MutableCFOptions moptions((ColumnFamilyOptions(options_)));
7902
+ EnvOptions eoptions(options_);
7697
7903
  TableReaderOptions toptions(
7698
7904
  ioptions, moptions.prefix_extractor,
7699
7905
  /*_compression_manager=*/nullptr, eoptions, ioptions.internal_comparator,
@@ -7711,7 +7917,7 @@ TEST_F(UserDefinedIndexTest, BasicTest) {
7711
7917
  uint64_t file_size = 0;
7712
7918
  std::unique_ptr<FSRandomAccessFile> file;
7713
7919
  std::unique_ptr<RandomAccessFileReader> file_reader;
7714
- const auto& fs = options.env->GetFileSystem();
7920
+ const auto& fs = options_.env->GetFileSystem();
7715
7921
  ASSERT_OK(fs->GetFileSize(ingest_file, IOOptions(), &file_size, nullptr));
7716
7922
  ASSERT_OK(fs->NewRandomAccessFile(ingest_file, eoptions, &file, nullptr));
7717
7923
  file_reader.reset(new RandomAccessFileReader(std::move(file), ingest_file));
@@ -7728,7 +7934,7 @@ TEST_F(UserDefinedIndexTest, BasicTest) {
7728
7934
  ASSERT_GE(block_handle.size(),
7729
7935
  expected_entries); // At least this many entries
7730
7936
 
7731
- std::unique_ptr<SstFileReader> reader(new SstFileReader(options));
7937
+ std::unique_ptr<SstFileReader> reader(new SstFileReader(options_));
7732
7938
  ASSERT_OK(reader->Open(ingest_file));
7733
7939
 
7734
7940
  ReadOptions ro;
@@ -7749,68 +7955,85 @@ TEST_F(UserDefinedIndexTest, BasicTest) {
7749
7955
  iter.reset(reader->NewIterator(ro));
7750
7956
  ASSERT_NE(iter, nullptr);
7751
7957
 
7752
- // Test that we can read all the keys
7958
+ // Test seek specific key
7753
7959
  key_count = 0;
7754
- for (iter->Seek("key09"); iter->Valid(); iter->Next()) {
7960
+ for (iter->Seek("key40"); iter->Valid(); iter->Next()) {
7755
7961
  key_count++;
7756
7962
  }
7757
- ASSERT_EQ(key_count, 91);
7963
+ ASSERT_EQ(key_count, is_reverse_comparator_ ? 41 : 60);
7758
7964
  ASSERT_OK(iter->status());
7759
7965
 
7760
- Slice ub("key75");
7966
+ // Test upper bound
7967
+ Slice ub(is_reverse_comparator_ ? "key25" : "key75");
7761
7968
  ro.iterate_upper_bound = &ub;
7762
7969
  iter.reset(reader->NewIterator(ro));
7763
7970
  ASSERT_NE(iter, nullptr);
7764
7971
 
7765
- // Test that we can read all the keys
7972
+ // Test seek specific key with upper bound
7766
7973
  key_count = 0;
7767
- for (iter->Seek("key09"); iter->Valid(); iter->Next()) {
7974
+ for (iter->Seek("key40"); iter->Valid(); iter->Next()) {
7768
7975
  key_count++;
7769
7976
  }
7770
- ASSERT_EQ(key_count, 66);
7977
+ ASSERT_EQ(key_count, is_reverse_comparator_ ? 15 : 35);
7771
7978
  ASSERT_OK(iter->status());
7772
7979
 
7773
7980
  user_defined_index_factory->seek_error_count_ = 1;
7774
7981
  iter.reset(reader->NewIterator(ro));
7775
7982
  ASSERT_NE(iter, nullptr);
7776
- iter->Seek("key09");
7983
+ iter->Seek("key40");
7777
7984
  ASSERT_NOK(iter->status());
7778
7985
 
7779
7986
  user_defined_index_factory->seek_error_count_ = 0;
7780
7987
  user_defined_index_factory->next_error_count_ = 1;
7781
7988
  iter.reset(reader->NewIterator(ro));
7782
7989
  ASSERT_NE(iter, nullptr);
7783
- iter->Seek("key09");
7784
- ASSERT_OK(iter->status());
7785
- iter->Next();
7990
+ iter->Seek(is_reverse_comparator_ ? "key92" : "key09");
7786
7991
  ASSERT_OK(iter->status());
7787
7992
  iter->Next();
7788
7993
  ASSERT_OK(iter->status());
7789
7994
  iter->Next();
7995
+ if (!is_reverse_comparator_) {
7996
+ ASSERT_OK(iter->status());
7997
+ iter->Next();
7998
+ }
7790
7999
  ASSERT_NOK(iter->status());
7791
8000
  user_defined_index_factory->next_error_count_ = 0;
7792
8001
 
7793
- ro.iterate_upper_bound = nullptr;
8002
+ ro.iterate_upper_bound = &ub;
7794
8003
  iter.reset(reader->NewIterator(ro));
7795
8004
  ASSERT_NE(iter, nullptr);
7796
- std::vector<ScanOptions> scan_opts({ScanOptions("key20")});
7797
- ;
7798
- scan_opts[0].property_bag.emplace().emplace("count", std::to_string(25));
8005
+ MultiScanArgs scan_opts(comparator_);
8006
+
8007
+ std::unordered_map<std::string, std::string> property_bag;
8008
+ property_bag["count"] = std::to_string(25);
8009
+ std::vector<std::string> boundaries = {"key10", "key50"};
8010
+ if (is_reverse_comparator_) {
8011
+ std::reverse(boundaries.begin(), boundaries.end());
8012
+ }
8013
+
8014
+ scan_opts.insert(boundaries[0], boundaries[1], std::optional(property_bag));
7799
8015
  iter->Prepare(scan_opts);
7800
- // Test that we can read all the keys
8016
+ // Test that UDI is used to help fetch the number of keys
7801
8017
  key_count = 0;
7802
- for (iter->Seek(scan_opts[0].range.start.value()); iter->Valid();
7803
- iter->Next()) {
8018
+ ub = boundaries[1];
8019
+ for (iter->Seek(scan_opts.GetScanRanges()[0].range.start.value());
8020
+ iter->Valid(); iter->Next()) {
7804
8021
  key_count++;
7805
8022
  }
7806
- ASSERT_GE(key_count, 25);
7807
8023
  // The index may undercount by 2 blocks
7808
- ASSERT_LE(key_count, 30);
8024
+ ASSERT_EQ(key_count, 29);
7809
8025
  ASSERT_OK(iter->status());
7810
8026
  }
7811
8027
 
7812
- TEST_F(UserDefinedIndexTest, InvalidArgumentTest1) {
7813
- Options options;
8028
+ TEST_P(UserDefinedIndexTest, BasicTestWithPartitionedIndex) {
8029
+ BasicTest(/*use_partitioned_index=*/true);
8030
+ }
8031
+
8032
+ TEST_P(UserDefinedIndexTest, BasicTestWithoutPartitionedIndex) {
8033
+ BasicTest(/*use_partitioned_index=*/false);
8034
+ }
8035
+
8036
+ TEST_P(UserDefinedIndexTest, InvalidArgumentTest1) {
7814
8037
  BlockBasedTableOptions table_options;
7815
8038
  std::string dbname = test::PerThreadDBPath("user_defined_index_test");
7816
8039
  std::string ingest_file = dbname + "test.sst";
@@ -7824,11 +8047,11 @@ TEST_F(UserDefinedIndexTest, InvalidArgumentTest1) {
7824
8047
  table_options.flush_block_policy_factory =
7825
8048
  std::make_shared<CustomFlushBlockPolicyFactory>();
7826
8049
 
7827
- options.table_factory.reset(NewBlockBasedTableFactory(table_options));
7828
- options.compression_opts.parallel_threads = 10;
8050
+ options_.table_factory.reset(NewBlockBasedTableFactory(table_options));
8051
+ options_.compression_opts.parallel_threads = 10;
7829
8052
 
7830
8053
  std::unique_ptr<SstFileWriter> writer;
7831
- writer.reset(new SstFileWriter(EnvOptions(), options));
8054
+ writer.reset(new SstFileWriter(EnvOptions(), options_));
7832
8055
  ASSERT_OK(writer->Open(ingest_file));
7833
8056
 
7834
8057
  std::string key = "foo";
@@ -7838,8 +8061,7 @@ TEST_F(UserDefinedIndexTest, InvalidArgumentTest1) {
7838
8061
  writer.reset();
7839
8062
  }
7840
8063
 
7841
- TEST_F(UserDefinedIndexTest, InvalidArgumentTest2) {
7842
- Options options;
8064
+ TEST_P(UserDefinedIndexTest, InvalidArgumentTest2) {
7843
8065
  BlockBasedTableOptions table_options;
7844
8066
  std::string dbname = test::PerThreadDBPath("user_defined_index_test");
7845
8067
  std::string ingest_file = dbname + "test.sst";
@@ -7853,10 +8075,10 @@ TEST_F(UserDefinedIndexTest, InvalidArgumentTest2) {
7853
8075
  table_options.flush_block_policy_factory =
7854
8076
  std::make_shared<CustomFlushBlockPolicyFactory>();
7855
8077
 
7856
- options.table_factory.reset(NewBlockBasedTableFactory(table_options));
8078
+ options_.table_factory.reset(NewBlockBasedTableFactory(table_options));
7857
8079
 
7858
8080
  std::unique_ptr<SstFileWriter> writer;
7859
- writer.reset(new SstFileWriter(EnvOptions(), options));
8081
+ writer.reset(new SstFileWriter(EnvOptions(), options_));
7860
8082
  ASSERT_OK(writer->Open(ingest_file));
7861
8083
 
7862
8084
  std::string key = "foo";
@@ -7866,8 +8088,7 @@ TEST_F(UserDefinedIndexTest, InvalidArgumentTest2) {
7866
8088
  writer.reset();
7867
8089
  }
7868
8090
 
7869
- TEST_F(UserDefinedIndexTest, IngestTest) {
7870
- Options options;
8091
+ TEST_P(UserDefinedIndexTest, IngestTest) {
7871
8092
  BlockBasedTableOptions table_options;
7872
8093
  std::string dbname = test::PerThreadDBPath("user_defined_index_test");
7873
8094
  std::string ingest_file = dbname + "test.sst";
@@ -7881,30 +8102,27 @@ TEST_F(UserDefinedIndexTest, IngestTest) {
7881
8102
  table_options.flush_block_policy_factory =
7882
8103
  std::make_shared<CustomFlushBlockPolicyFactory>();
7883
8104
 
7884
- options.table_factory.reset(NewBlockBasedTableFactory(table_options));
8105
+ options_.table_factory.reset(NewBlockBasedTableFactory(table_options));
7885
8106
 
7886
8107
  std::unique_ptr<SstFileWriter> writer;
7887
- writer.reset(new SstFileWriter(EnvOptions(), options));
8108
+ writer.reset(new SstFileWriter(EnvOptions(), options_));
7888
8109
  ASSERT_OK(writer->Open(ingest_file));
7889
8110
 
7890
- // Add 100 keys instead of just 5
7891
- for (int i = 0; i < 100; i++) {
7892
- std::stringstream ss;
7893
- ss << std::setw(2) << std::setfill('0') << i;
7894
- std::string key = "key" + ss.str();
7895
- std::string value = "value" + ss.str();
7896
- ASSERT_OK(writer->Put(key, value));
8111
+ auto kvs = generateKVs(/*key_count*/ 100);
8112
+ for (const auto& kv : kvs) {
8113
+ ASSERT_OK(writer->Put(kv.first, kv.second));
7897
8114
  }
8115
+
7898
8116
  ASSERT_OK(writer->Finish());
7899
8117
  writer.reset();
7900
8118
 
7901
8119
  std::unique_ptr<DB> db;
7902
- options.create_if_missing = true;
7903
- Status s = DB::Open(options, dbname, &db);
8120
+ options_.create_if_missing = true;
8121
+ Status s = DB::Open(options_, dbname, &db);
7904
8122
  ASSERT_OK(s);
7905
8123
  ASSERT_TRUE(db != nullptr);
7906
8124
  ColumnFamilyHandle* cfh = nullptr;
7907
- ASSERT_OK(db->CreateColumnFamily(options, "new_cf", &cfh));
8125
+ ASSERT_OK(db->CreateColumnFamily(options_, "new_cf", &cfh));
7908
8126
 
7909
8127
  IngestExternalFileOptions ifo;
7910
8128
  s = db->IngestExternalFile(cfh, {ingest_file}, ifo);
@@ -7928,55 +8146,160 @@ TEST_F(UserDefinedIndexTest, IngestTest) {
7928
8146
  iter.reset(db->NewIterator(ro, cfh));
7929
8147
  ASSERT_NE(iter, nullptr);
7930
8148
 
7931
- // Test that we can read all the keys
8149
+ // Test seek specific key
7932
8150
  key_count = 0;
7933
- for (iter->Seek("key09"); iter->Valid(); iter->Next()) {
8151
+ for (iter->Seek("key40"); iter->Valid(); iter->Next()) {
7934
8152
  key_count++;
7935
8153
  }
7936
- ASSERT_EQ(key_count, 91);
8154
+ ASSERT_EQ(key_count, is_reverse_comparator_ ? 41 : 60);
7937
8155
  ASSERT_OK(iter->status());
7938
8156
 
7939
- Slice ub("key75");
8157
+ // Test upper bound
8158
+ Slice ub(is_reverse_comparator_ ? "key25" : "key75");
7940
8159
  ro.iterate_upper_bound = &ub;
7941
8160
  iter.reset(db->NewIterator(ro, cfh));
7942
8161
  ASSERT_NE(iter, nullptr);
7943
8162
 
7944
- // Test that we can read all the keys
8163
+ // Test seek specific key with upper bound
7945
8164
  key_count = 0;
7946
- for (iter->Seek("key09"); iter->Valid(); iter->Next()) {
8165
+ for (iter->Seek("key40"); iter->Valid(); iter->Next()) {
7947
8166
  key_count++;
7948
8167
  }
7949
- ASSERT_EQ(key_count, 66);
8168
+ ASSERT_EQ(key_count, is_reverse_comparator_ ? 15 : 35);
7950
8169
  ASSERT_OK(iter->status());
8170
+ iter.reset();
7951
8171
 
7952
- ro.iterate_upper_bound = nullptr;
7953
- iter.reset(db->NewIterator(ro, cfh));
8172
+ ASSERT_OK(db->DestroyColumnFamilyHandle(cfh));
8173
+ ASSERT_OK(db->Close());
8174
+ ASSERT_OK(DestroyDB(dbname, options_));
8175
+ }
8176
+
8177
+ TEST_P(UserDefinedIndexTest, EmptyRangeTest) {
8178
+ BlockBasedTableOptions table_options;
8179
+ std::string dbname = test::PerThreadDBPath("user_defined_index_test");
8180
+ std::string ingest_file = dbname + "test.sst";
8181
+
8182
+ // Set up the user-defined index factory
8183
+ auto user_defined_index_factory =
8184
+ std::make_shared<TestUserDefinedIndexFactory>();
8185
+ table_options.user_defined_index_factory = user_defined_index_factory;
8186
+
8187
+ // Set up custom flush block policy that flushes every 3 keys
8188
+ table_options.flush_block_policy_factory =
8189
+ std::make_shared<CustomFlushBlockPolicyFactory>();
8190
+
8191
+ options_.table_factory.reset(NewBlockBasedTableFactory(table_options));
8192
+
8193
+ std::unique_ptr<SstFileWriter> writer;
8194
+ writer.reset(new SstFileWriter(EnvOptions(), options_));
8195
+ ASSERT_OK(writer->Open(ingest_file));
8196
+
8197
+ // Generate key range key0 ~ key19, key40 ~ key59, key80 ~ key99
8198
+ std::vector<std::pair<std::string, std::string>> kvs;
8199
+ bool skip = false;
8200
+ for (int i = 0; i < 100; i++) {
8201
+ if (i > 0 && i % 20 == 0) {
8202
+ skip = !skip;
8203
+ }
8204
+ if (skip) {
8205
+ continue;
8206
+ }
8207
+ std::stringstream ss;
8208
+ ss << std::setw(2) << std::setfill('0') << i;
8209
+ std::string key = "key" + ss.str();
8210
+ std::string value = "value" + ss.str();
8211
+ kvs.emplace_back(key, value);
8212
+ }
8213
+
8214
+ if (is_reverse_comparator_) {
8215
+ std::reverse(kvs.begin(), kvs.end());
8216
+ }
8217
+
8218
+ for (const auto& kv : kvs) {
8219
+ ASSERT_OK(writer->Put(kv.first, kv.second));
8220
+ }
8221
+ ASSERT_OK(writer->Finish());
8222
+ writer.reset();
8223
+
8224
+ std::unique_ptr<DB> db;
8225
+ options_.create_if_missing = true;
8226
+ Status s = DB::Open(options_, dbname, &db);
8227
+ ASSERT_OK(s);
8228
+ ASSERT_TRUE(db != nullptr);
8229
+ ColumnFamilyHandle* cfh = nullptr;
8230
+ ASSERT_OK(db->CreateColumnFamily(options_, "new_cf", &cfh));
8231
+
8232
+ IngestExternalFileOptions ifo;
8233
+ s = db->IngestExternalFile(cfh, {ingest_file}, ifo);
8234
+ ASSERT_OK(s);
8235
+
8236
+ ReadOptions ro;
8237
+ std::unique_ptr<Iterator> iter(db->NewIterator(ro, cfh));
7954
8238
  ASSERT_NE(iter, nullptr);
7955
- std::vector<ScanOptions> scan_opts({ScanOptions("key20")});
7956
- ;
7957
- scan_opts[0].property_bag.emplace().emplace("count", std::to_string(25));
7958
- iter->Prepare(scan_opts);
8239
+ ASSERT_OK(iter->status());
8240
+
7959
8241
  // Test that we can read all the keys
7960
- key_count = 0;
7961
- for (iter->Seek(scan_opts[0].range.start.value()); iter->Valid();
7962
- iter->Next()) {
8242
+ int key_count = 0;
8243
+ for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
7963
8244
  key_count++;
7964
8245
  }
7965
- ASSERT_GE(key_count, 25);
7966
- // The index may undercount by 2 blocks
7967
- ASSERT_LE(key_count, 30);
8246
+ ASSERT_EQ(key_count, 60);
7968
8247
  ASSERT_OK(iter->status());
7969
8248
  iter.reset();
7970
8249
 
8250
+ ro.table_index_factory = user_defined_index_factory.get();
8251
+ std::vector<int> key_counts;
8252
+ MultiScanArgs scan_opts(options_.comparator);
8253
+ std::unordered_map<std::string, std::string> property_bag;
8254
+ property_bag["count"] = std::to_string(5);
8255
+
8256
+ ValidateMultiScan({{{"key25", "key30"}, 0, 0},
8257
+ {{"key33", "key37"}, 0, 0},
8258
+ // Non-empty scan with range greater than count
8259
+ // In the key42:key56 range, we might read an additional
8260
+ // block worth of keys due to the boundaries (5 + 3)
8261
+ {{"key42", "key56"}, 8, 7},
8262
+ // Empty scan succeeding a non-empty one
8263
+ {{"key65", "key70"}, 0, 0},
8264
+ // A non-empty scan with range smaller than count
8265
+ {{"key85", "key87"}, 2, 2},
8266
+ // Scan range completely outside the DB
8267
+ {{"key991", "key999"}, 0, 0}},
8268
+ property_bag, ro, scan_opts, key_counts, db, cfh);
8269
+
8270
+ // Scans that overlap with part of key range, with overlap less than count
8271
+ ValidateMultiScan({{{"key18", "key25"}, 2, 1}, {{"key38", "key43"}, 3, 4}},
8272
+ property_bag, ro, scan_opts, key_counts, db, cfh);
8273
+
8274
+ // Scans that overlap with part of key range, with overlap same as count
8275
+ ValidateMultiScan({{{"key15", "key26"}, 5, 4}, {{"key38", "key46"}, 6, 7}},
8276
+ property_bag, ro, scan_opts, key_counts, db, cfh);
8277
+
8278
+ // Scans that overlap with part of key range, with overlap greater than count
8279
+ ValidateMultiScan({{{"key10", "key26"}, 8, 8},
8280
+ // Cross block boundary
8281
+ {{"key38", "key49"}, 7, 9}},
8282
+ property_bag, ro, scan_opts, key_counts, db, cfh);
8283
+
8284
+ // Scan bigger than one contiguous range of keys, with overlap greater than
8285
+ // count
8286
+ ValidateMultiScan({{{"key75", "key991"}, 8, 9}}, property_bag, ro, scan_opts,
8287
+ key_counts, db, cfh);
8288
+
8289
+ // Scan bigger than one contiguous range of keys, with overlap less than count
8290
+ property_bag["count"] = std::to_string(25);
8291
+ ValidateMultiScan({{{"key75", "key991"}, 20, 20}}, property_bag, ro,
8292
+ scan_opts, key_counts, db, cfh);
8293
+
7971
8294
  ASSERT_OK(db->DestroyColumnFamilyHandle(cfh));
7972
8295
  ASSERT_OK(db->Close());
7973
- ASSERT_OK(DestroyDB(dbname, options));
8296
+ ASSERT_OK(DestroyDB(dbname, options_));
7974
8297
  }
7975
8298
 
7976
8299
  // Verify that external file ingestion fails if we try to ingest an SST file
7977
8300
  // without the UDI and a UDI factory is configured in BlockBasedTableOptions
7978
- TEST_F(UserDefinedIndexTest, IngestFailTest) {
7979
- Options options;
8301
+ // and fail_if_no_udi_on_open is true in BlockBasedTableOptions.
8302
+ TEST_P(UserDefinedIndexTest, IngestFailTest) {
7980
8303
  BlockBasedTableOptions table_options;
7981
8304
  std::string dbname = test::PerThreadDBPath("user_defined_index_test");
7982
8305
  std::string ingest_file = dbname + "test.sst";
@@ -7985,19 +8308,15 @@ TEST_F(UserDefinedIndexTest, IngestFailTest) {
7985
8308
  table_options.flush_block_policy_factory =
7986
8309
  std::make_shared<CustomFlushBlockPolicyFactory>();
7987
8310
 
7988
- options.table_factory.reset(NewBlockBasedTableFactory(table_options));
8311
+ options_.table_factory.reset(NewBlockBasedTableFactory(table_options));
7989
8312
 
7990
8313
  std::unique_ptr<SstFileWriter> writer;
7991
- writer.reset(new SstFileWriter(EnvOptions(), options));
8314
+ writer.reset(new SstFileWriter(EnvOptions(), options_));
7992
8315
  ASSERT_OK(writer->Open(ingest_file));
7993
8316
 
7994
- // Add 100 keys instead of just 5
7995
- for (int i = 0; i < 100; i++) {
7996
- std::stringstream ss;
7997
- ss << std::setw(2) << std::setfill('0') << i;
7998
- std::string key = "key" + ss.str();
7999
- std::string value = "value" + ss.str();
8000
- ASSERT_OK(writer->Put(key, value));
8317
+ auto kvs = generateKVs(/*key_count*/ 100);
8318
+ for (const auto& kv : kvs) {
8319
+ ASSERT_OK(writer->Put(kv.first, kv.second));
8001
8320
  }
8002
8321
  ASSERT_OK(writer->Finish());
8003
8322
  writer.reset();
@@ -8006,24 +8325,1090 @@ TEST_F(UserDefinedIndexTest, IngestFailTest) {
8006
8325
  auto user_defined_index_factory =
8007
8326
  std::make_shared<TestUserDefinedIndexFactory>();
8008
8327
  table_options.user_defined_index_factory = user_defined_index_factory;
8009
- options.table_factory.reset(NewBlockBasedTableFactory(table_options));
8328
+ table_options.fail_if_no_udi_on_open = true;
8329
+ options_.table_factory.reset(NewBlockBasedTableFactory(table_options));
8010
8330
 
8011
8331
  std::unique_ptr<DB> db;
8012
- options.create_if_missing = true;
8013
- Status s = DB::Open(options, dbname, &db);
8332
+ options_.create_if_missing = true;
8333
+ Status s = DB::Open(options_, dbname, &db);
8014
8334
  ASSERT_OK(s);
8015
8335
  ASSERT_TRUE(db != nullptr);
8016
8336
  ColumnFamilyHandle* cfh = nullptr;
8017
- ASSERT_OK(db->CreateColumnFamily(options, "new_cf", &cfh));
8337
+ ASSERT_OK(db->CreateColumnFamily(options_, "new_cf", &cfh));
8018
8338
 
8019
8339
  IngestExternalFileOptions ifo;
8020
8340
  s = db->IngestExternalFile(cfh, {ingest_file}, ifo);
8021
8341
  ASSERT_NOK(s);
8022
8342
 
8343
+ ASSERT_OK(db->SetOptions(
8344
+ cfh, {{"block_based_table_factory", "{fail_if_no_udi_on_open=false;}"}}));
8345
+ s = db->IngestExternalFile(cfh, {ingest_file}, ifo);
8346
+ ASSERT_OK(s);
8347
+
8023
8348
  ASSERT_OK(db->DestroyColumnFamilyHandle(cfh));
8024
8349
  ASSERT_OK(db->Close());
8025
- ASSERT_OK(DestroyDB(dbname, options));
8350
+ ASSERT_OK(DestroyDB(dbname, options_));
8351
+ }
8352
+
8353
+ TEST_P(UserDefinedIndexTest, IngestEmptyUDI) {
8354
+ BlockBasedTableOptions table_options;
8355
+ std::string dbname = test::PerThreadDBPath("user_defined_index_test");
8356
+ std::string ingest_file = dbname + "test.sst";
8357
+ std::string ingest_file2 = dbname + "dummy.sst";
8358
+
8359
+ // Set up the user-defined index factory
8360
+ auto user_defined_index_factory =
8361
+ std::make_shared<TestUserDefinedIndexFactory>();
8362
+ table_options.user_defined_index_factory = user_defined_index_factory;
8363
+ // Set up custom flush block policy that flushes every 3 keys
8364
+ table_options.flush_block_policy_factory =
8365
+ std::make_shared<CustomFlushBlockPolicyFactory>();
8366
+
8367
+ options_.table_factory.reset(NewBlockBasedTableFactory(table_options));
8368
+
8369
+ std::unique_ptr<SstFileWriter> writer;
8370
+ writer.reset(new SstFileWriter(EnvOptions(), options_));
8371
+ ASSERT_OK(writer->Open(ingest_file));
8372
+
8373
+ auto kvs = generateKVs(/*key_count*/ 100);
8374
+ for (const auto& kv : kvs) {
8375
+ ASSERT_OK(writer->Put(kv.first, kv.second));
8376
+ }
8377
+ ASSERT_OK(writer->Finish());
8378
+ writer.reset();
8379
+ writer.reset(new SstFileWriter(EnvOptions(), options_));
8380
+ ASSERT_OK(writer->Open(ingest_file2));
8381
+ ASSERT_OK(writer->Put("dummy", "val"));
8382
+ ASSERT_OK(writer->Finish());
8383
+ writer.reset();
8384
+
8385
+ table_options.fail_if_no_udi_on_open = true;
8386
+ options_.table_factory.reset(NewBlockBasedTableFactory(table_options));
8387
+
8388
+ std::unique_ptr<DB> db;
8389
+ options_.create_if_missing = true;
8390
+ Status s = DB::Open(options_, dbname, &db);
8391
+ ASSERT_OK(s);
8392
+ ASSERT_TRUE(db != nullptr);
8393
+ ColumnFamilyHandle* cfh = nullptr;
8394
+ ASSERT_OK(db->CreateColumnFamily(options_, "new_cf", &cfh));
8395
+
8396
+ std::vector<IngestExternalFileArg> ifa;
8397
+ ifa.emplace_back();
8398
+ ifa[0].column_family = cfh;
8399
+ ifa[0].external_files.emplace_back(ingest_file);
8400
+ ifa[0].external_files.emplace_back(ingest_file2);
8401
+ s = db->IngestExternalFiles(ifa);
8402
+ ASSERT_OK(s);
8403
+
8404
+ ASSERT_OK(db->DestroyColumnFamilyHandle(cfh));
8405
+ ASSERT_OK(db->Close());
8406
+ ASSERT_OK(DestroyDB(dbname, options_));
8026
8407
  }
8408
+
8409
+ TEST_P(UserDefinedIndexTest, MultiScanFailureTest) {
8410
+ BlockBasedTableOptions table_options;
8411
+ std::string dbname = test::PerThreadDBPath("user_defined_index_test");
8412
+ std::string ingest_file = dbname + "test.sst";
8413
+
8414
+ // Set up the user-defined index factory
8415
+ auto user_defined_index_factory =
8416
+ std::make_shared<TestUserDefinedIndexFactory>();
8417
+ table_options.user_defined_index_factory = user_defined_index_factory;
8418
+
8419
+ // Set up custom flush block policy that flushes every 3 keys
8420
+ table_options.flush_block_policy_factory =
8421
+ std::make_shared<CustomFlushBlockPolicyFactory>();
8422
+
8423
+ options_.table_factory.reset(NewBlockBasedTableFactory(table_options));
8424
+
8425
+ std::unique_ptr<SstFileWriter> writer;
8426
+ writer.reset(new SstFileWriter(EnvOptions(), options_));
8427
+ ASSERT_OK(writer->Open(ingest_file));
8428
+
8429
+ // Use bigger value, so that prefetch size limit will be effective
8430
+ auto kvs = generateKVs(/*key_count*/ 100, /* value_size */ 1024);
8431
+ for (const auto& kv : kvs) {
8432
+ ASSERT_OK(writer->Put(kv.first, kv.second));
8433
+ }
8434
+ ASSERT_OK(writer->Finish());
8435
+ writer.reset();
8436
+
8437
+ std::unique_ptr<DB> db;
8438
+ options_.create_if_missing = true;
8439
+ Status s = DB::Open(options_, dbname, &db);
8440
+ ASSERT_OK(s);
8441
+ ASSERT_TRUE(db != nullptr);
8442
+ ColumnFamilyHandle* cfh = nullptr;
8443
+ ASSERT_OK(db->CreateColumnFamily(options_, "new_cf", &cfh));
8444
+
8445
+ IngestExternalFileOptions ifo;
8446
+ s = db->IngestExternalFile(cfh, {ingest_file}, ifo);
8447
+ ASSERT_OK(s);
8448
+
8449
+ std::vector<std::string> key_ranges({"key03", "key05", "key12", "key14"});
8450
+ ReadOptions ro;
8451
+ ro.table_index_factory = user_defined_index_factory.get();
8452
+ Slice ub;
8453
+ ro.iterate_upper_bound = &ub;
8454
+ std::unordered_map<std::string, std::string> property_bag;
8455
+ property_bag["count"] = std::to_string(5);
8456
+ MultiScanArgs scan_options(comparator_);
8457
+ if (is_reverse_comparator_) {
8458
+ std::reverse(key_ranges.begin(), key_ranges.end());
8459
+ }
8460
+ scan_options.insert(key_ranges[0], key_ranges[1], property_bag);
8461
+ scan_options.insert(key_ranges[2], key_ranges[3], property_bag);
8462
+ scan_options.max_prefetch_size = 3500;
8463
+ std::unique_ptr<Iterator> iter(db->NewIterator(ro, cfh));
8464
+ ASSERT_NE(iter, nullptr);
8465
+ iter->Prepare(scan_options);
8466
+ int count = 0;
8467
+ ub = key_ranges[1];
8468
+ iter->Seek(key_ranges[0]);
8469
+ while (iter->status().ok() && iter->Valid()) {
8470
+ ASSERT_GE(comparator_->Compare(iter->key(), key_ranges[0]), 0);
8471
+ ASSERT_LT(comparator_->Compare(iter->key(), key_ranges[1]), 0);
8472
+ count++;
8473
+ iter->Next();
8474
+ }
8475
+ ASSERT_OK(iter->status()) << iter->status().ToString();
8476
+ ASSERT_EQ(count, 2);
8477
+
8478
+ ub = key_ranges[3];
8479
+ iter->Seek(key_ranges[2]);
8480
+ // This should fail due to reaching max_prefetch_size limit
8481
+ ASSERT_EQ(iter->status(), Status::Incomplete());
8482
+ iter.reset();
8483
+
8484
+ // Empty range multiscan error
8485
+ iter.reset(db->NewIterator(ro, cfh));
8486
+ scan_options = MultiScanArgs(comparator_);
8487
+ iter->Prepare(scan_options);
8488
+ ASSERT_EQ(iter->status(), Status::InvalidArgument("Empty MultiScanArgs"));
8489
+
8490
+ // Check no seek key error
8491
+ iter.reset(db->NewIterator(ro, cfh));
8492
+ scan_options = MultiScanArgs(comparator_);
8493
+ scan_options.insert(key_ranges[0], key_ranges[2], property_bag);
8494
+ iter->Prepare(scan_options);
8495
+ iter->SeekToFirst();
8496
+ ASSERT_EQ(iter->status(),
8497
+ Status::InvalidArgument("No seek key for MultiScan"));
8498
+
8499
+ // Seek is not allowed to seen a key that is not following the prepare order
8500
+ iter.reset(db->NewIterator(ro, cfh));
8501
+ ASSERT_NE(iter, nullptr);
8502
+ scan_options.max_prefetch_size = 0;
8503
+ iter->Prepare(scan_options);
8504
+ ub = key_ranges[3];
8505
+ iter->Seek(key_ranges[2]);
8506
+ ASSERT_EQ(
8507
+ iter->status(),
8508
+ Status::InvalidArgument(
8509
+ "Seek target does not match the start of the next prepared range at "
8510
+ "index 0"));
8511
+ ASSERT_FALSE(iter->Valid());
8512
+ iter.reset();
8513
+
8514
+ // limit is equal to start error
8515
+ iter.reset(db->NewIterator(ro, cfh));
8516
+ ASSERT_NE(iter, nullptr);
8517
+ (*scan_options).clear();
8518
+ scan_options.insert(key_ranges[0], key_ranges[0], property_bag);
8519
+ iter->Prepare(scan_options);
8520
+ ASSERT_EQ(iter->status(),
8521
+ Status::InvalidArgument(
8522
+ "Scan start key is large or equal than limit at index 0"));
8523
+ iter.reset();
8524
+
8525
+ // overlapping ranges error
8526
+ iter.reset(db->NewIterator(ro, cfh));
8527
+ ASSERT_NE(iter, nullptr);
8528
+ (*scan_options).clear();
8529
+ scan_options.insert(key_ranges[0], key_ranges[2], property_bag);
8530
+ scan_options.insert(key_ranges[1], key_ranges[3], property_bag);
8531
+ iter->Prepare(scan_options);
8532
+ ASSERT_EQ(iter->status(),
8533
+ Status::InvalidArgument("Overlapping ranges at index 1"));
8534
+ iter.reset();
8535
+
8536
+ // Validate an error is returned if upper bound is not set to the same value
8537
+ // as limit
8538
+ iter.reset(db->NewIterator(ro, cfh));
8539
+ scan_options = MultiScanArgs(comparator_);
8540
+ scan_options.insert(key_ranges[0], key_ranges[1], property_bag);
8541
+ iter->Prepare(scan_options);
8542
+ ub = "";
8543
+ iter->Seek(key_ranges[0]);
8544
+ ASSERT_EQ(iter->status(),
8545
+ Status::InvalidArgument(
8546
+ "Upper bound is not set to the same limit value of the next "
8547
+ "prepared range at index 0"));
8548
+ ASSERT_FALSE(iter->Valid());
8549
+
8550
+ // Validate an error is returned when seek more keys than prepared
8551
+ iter.reset(db->NewIterator(ro, cfh));
8552
+ scan_options = MultiScanArgs(comparator_);
8553
+ scan_options.insert(key_ranges[0], key_ranges[1], property_bag);
8554
+ iter->Prepare(scan_options);
8555
+ ub = key_ranges[1];
8556
+ iter->Seek(key_ranges[0]);
8557
+ ASSERT_OK(iter->status());
8558
+ ASSERT_TRUE(iter->Valid());
8559
+ iter->Seek(key_ranges[2]);
8560
+ ASSERT_EQ(iter->status(),
8561
+ Status::InvalidArgument(
8562
+ "Seek called after exhausting all of the scan ranges"));
8563
+ ASSERT_FALSE(iter->Valid());
8564
+ iter.reset();
8565
+
8566
+ // Check error is returned if upper bound is not set and limit is set
8567
+ ro.iterate_upper_bound = nullptr;
8568
+ iter.reset(db->NewIterator(ro, cfh));
8569
+ scan_options = MultiScanArgs(comparator_);
8570
+ scan_options.insert(key_ranges[0], key_ranges[1], property_bag);
8571
+ iter->Prepare(scan_options);
8572
+ iter->Seek(key_ranges[0]);
8573
+ ASSERT_EQ(iter->status(),
8574
+ Status::InvalidArgument(
8575
+ "Upper bound is not set to the same limit value of the next "
8576
+ "prepared range at index 0"));
8577
+ ASSERT_FALSE(iter->Valid());
8578
+ iter.reset();
8579
+
8580
+ // Upper bound is allowed to be empty, if limit is not set
8581
+ ro.iterate_upper_bound = nullptr;
8582
+ iter.reset(db->NewIterator(ro, cfh));
8583
+ scan_options = MultiScanArgs(comparator_);
8584
+ scan_options.insert(key_ranges[0], property_bag);
8585
+ iter->Prepare(scan_options);
8586
+ iter->Seek(key_ranges[0]);
8587
+ ASSERT_OK(iter->status());
8588
+ ASSERT_TRUE(iter->Valid());
8589
+ iter.reset();
8590
+
8591
+ ASSERT_OK(db->DestroyColumnFamilyHandle(cfh));
8592
+ ASSERT_OK(db->Close());
8593
+ ASSERT_OK(DestroyDB(dbname, options_));
8594
+ }
8595
+
8596
+ TEST_P(UserDefinedIndexTest, ConfigTest) {
8597
+ BlockBasedTableOptions table_options;
8598
+ std::string dbname = test::PerThreadDBPath("user_defined_index_test");
8599
+ std::string ingest_file = dbname + "test.sst";
8600
+
8601
+ // Set up the user-defined index factory
8602
+ auto user_defined_index_factory =
8603
+ std::make_shared<TestUserDefinedIndexFactory>();
8604
+ table_options.user_defined_index_factory = user_defined_index_factory;
8605
+
8606
+ // Set up custom flush block policy that flushes every 3 keys
8607
+ table_options.flush_block_policy_factory =
8608
+ std::make_shared<CustomFlushBlockPolicyFactory>();
8609
+
8610
+ options_.table_factory.reset(NewBlockBasedTableFactory(table_options));
8611
+
8612
+ std::unique_ptr<SstFileWriter> writer;
8613
+ writer.reset(new SstFileWriter(EnvOptions(), options_));
8614
+ ASSERT_OK(writer->Open(ingest_file));
8615
+
8616
+ auto kvs = generateKVs(/*key_count*/ 100);
8617
+ for (const auto& kv : kvs) {
8618
+ ASSERT_OK(writer->Put(kv.first, kv.second));
8619
+ }
8620
+ ASSERT_OK(writer->Finish());
8621
+ writer.reset();
8622
+
8623
+ table_options.user_defined_index_factory.reset();
8624
+ options_.table_factory.reset(NewBlockBasedTableFactory(table_options));
8625
+ // Set up the user-defined index factory
8626
+ ObjectLibrary::Default().get()->AddFactory<UserDefinedIndexFactory>(
8627
+ "test_index", [](const std::string& /* uri */,
8628
+ std::unique_ptr<UserDefinedIndexFactory>* guard,
8629
+ std::string* /* errmsg */) {
8630
+ auto factory = new TestUserDefinedIndexFactory();
8631
+ guard->reset(factory);
8632
+ return guard->get();
8633
+ });
8634
+ ASSERT_OK(GetColumnFamilyOptionsFromString(
8635
+ ConfigOptions(), options_,
8636
+ "block_based_table_factory={user_defined_index_factory=test_index;}",
8637
+ &options_));
8638
+
8639
+ std::unique_ptr<DB> db;
8640
+ options_.create_if_missing = true;
8641
+ Status s = DB::Open(options_, dbname, &db);
8642
+ ASSERT_OK(s);
8643
+ ASSERT_TRUE(db != nullptr);
8644
+ ColumnFamilyHandle* cfh = nullptr;
8645
+ ASSERT_OK(db->CreateColumnFamily(options_, "new_cf", &cfh));
8646
+
8647
+ IngestExternalFileOptions ifo;
8648
+ s = db->IngestExternalFile(cfh, {ingest_file}, ifo);
8649
+ ASSERT_OK(s);
8650
+
8651
+ ReadOptions ro;
8652
+ Slice ub;
8653
+ ro.iterate_upper_bound = &ub;
8654
+ ro.table_index_factory = user_defined_index_factory.get();
8655
+ std::unique_ptr<Iterator> iter(db->NewIterator(ro, cfh));
8656
+ ASSERT_NE(iter, nullptr);
8657
+ MultiScanArgs scan_opts(options_.comparator);
8658
+ std::unordered_map<std::string, std::string> property_bag;
8659
+ property_bag["count"] = std::to_string(25);
8660
+
8661
+ std::vector<std::string> boundaries = {"key10", "key50"};
8662
+ if (is_reverse_comparator_) {
8663
+ std::reverse(boundaries.begin(), boundaries.end());
8664
+ }
8665
+
8666
+ scan_opts.insert(boundaries[0], boundaries[1], std::optional(property_bag));
8667
+ iter->Prepare(scan_opts);
8668
+ // Test that UDI is used to help fetch the number of keys
8669
+ ub = boundaries[1];
8670
+ int key_count = 0;
8671
+ for (iter->Seek(scan_opts.GetScanRanges()[0].range.start.value());
8672
+ iter->Valid(); iter->Next()) {
8673
+ key_count++;
8674
+ }
8675
+ // Number of blocks prepared is based on UDI, it would be slightly higher than
8676
+ // the limit
8677
+ // The index may undercount by 2 blocks
8678
+ ASSERT_EQ(key_count, 29);
8679
+ ASSERT_OK(iter->status());
8680
+ iter.reset();
8681
+
8682
+ ASSERT_OK(db->DestroyColumnFamilyHandle(cfh));
8683
+ ASSERT_OK(db->Close());
8684
+ ASSERT_OK(DestroyDB(dbname, options_));
8685
+ }
8686
+
8687
+ TEST_P(UserDefinedIndexTest, RangeDelete) {
8688
+ BlockBasedTableOptions table_options;
8689
+ options_.num_levels = 50;
8690
+ options_.compaction_style = kCompactionStyleUniversal;
8691
+ options_.disable_auto_compactions = true;
8692
+ std::string dbname = test::PerThreadDBPath("user_defined_index_test");
8693
+ std::string ingest_file = dbname + "test.sst";
8694
+
8695
+ // Set up the user-defined index factory
8696
+ auto user_defined_index_factory =
8697
+ std::make_shared<TestUserDefinedIndexFactory>();
8698
+ table_options.user_defined_index_factory = user_defined_index_factory;
8699
+
8700
+ // Set up custom flush block policy that flushes every 3 keys
8701
+ table_options.flush_block_policy_factory =
8702
+ std::make_shared<CustomFlushBlockPolicyFactory>();
8703
+
8704
+ options_.table_factory.reset(NewBlockBasedTableFactory(table_options));
8705
+
8706
+ auto create_ingestion_data_file = [&](const std::string& filename) {
8707
+ std::unique_ptr<SstFileWriter> writer;
8708
+ writer.reset(new SstFileWriter(EnvOptions(), options_));
8709
+ ASSERT_OK(writer->Open(filename));
8710
+ auto kvs = generateKVs(100);
8711
+
8712
+ for (const auto& kv : kvs) {
8713
+ ASSERT_OK(writer->Put(kv.first, kv.second));
8714
+ }
8715
+ ASSERT_OK(writer->Finish());
8716
+ writer.reset();
8717
+ };
8718
+
8719
+ // Create first ingestion file with data
8720
+ create_ingestion_data_file(ingest_file + "_0");
8721
+
8722
+ // Create second ingestion file with range delete only that covers the first
8723
+ // file to delete all of its keys.
8724
+ {
8725
+ std::unique_ptr<SstFileWriter> writer;
8726
+ writer.reset(new SstFileWriter(EnvOptions(), options_));
8727
+ ASSERT_OK(writer->Open(ingest_file + "_1"));
8728
+ if (is_reverse_comparator_) {
8729
+ ASSERT_OK(writer->DeleteRange("keyz", "key"));
8730
+ } else {
8731
+ ASSERT_OK(writer->DeleteRange("key", "keyz"));
8732
+ }
8733
+ ASSERT_OK(writer->Finish());
8734
+ writer.reset();
8735
+ }
8736
+
8737
+ // Create the second ingestion file with data
8738
+ create_ingestion_data_file(ingest_file + "_2");
8739
+
8740
+ std::unique_ptr<DB> db;
8741
+ options_.create_if_missing = true;
8742
+ Status s = DB::Open(options_, dbname, &db);
8743
+ ASSERT_OK(s);
8744
+ ASSERT_TRUE(db != nullptr);
8745
+ ColumnFamilyHandle* cfh = nullptr;
8746
+ ASSERT_OK(db->CreateColumnFamily(options_, "new_cf", &cfh));
8747
+
8748
+ IngestExternalFileOptions ifo;
8749
+ // ingest first data file key00~key99
8750
+ s = db->IngestExternalFile(cfh, {ingest_file + "_0"}, ifo);
8751
+ ASSERT_OK(s);
8752
+ // ingest delete range (key-keyz) and new data file (key00-key99) together
8753
+ s = db->IngestExternalFile(cfh, {ingest_file + "_1", ingest_file + "_2"},
8754
+ ifo);
8755
+ ASSERT_OK(s);
8756
+
8757
+ std::vector<Slice> range = {
8758
+ Slice("key10"),
8759
+ Slice("key25"),
8760
+ Slice("key80"),
8761
+ Slice("key95"),
8762
+ };
8763
+
8764
+ if (is_reverse_comparator_) {
8765
+ std::reverse(range.begin(), range.end());
8766
+ }
8767
+
8768
+ Slice ub("");
8769
+ ReadOptions ro;
8770
+ ro.iterate_upper_bound = &ub;
8771
+ std::unique_ptr<Iterator> iter(db->NewIterator(ro, cfh));
8772
+ ASSERT_NE(iter, nullptr);
8773
+
8774
+ MultiScanArgs scan_opts(options_.comparator);
8775
+ std::unordered_map<std::string, std::string> property_bag;
8776
+ property_bag["count"] = std::to_string(9);
8777
+
8778
+ std::vector<std::vector<char>> decoded_ranges;
8779
+ for (size_t i = 0; i < range.size() / 2; i++) {
8780
+ scan_opts.insert(range[i * 2], range[i * 2 + 1],
8781
+ std::optional(property_bag));
8782
+ }
8783
+ iter->Prepare(scan_opts);
8784
+
8785
+ for (size_t i = 0; i < range.size() / 2; i++) {
8786
+ // Update upper bound before each seek
8787
+ ub = range[2 * i + 1];
8788
+ auto key_count = 0;
8789
+ for (iter->Seek(range[i * 2]); iter->Valid(); iter->Next()) {
8790
+ key_count++;
8791
+ }
8792
+ ASSERT_OK(iter->status());
8793
+ ASSERT_EQ(key_count, 15);
8794
+ }
8795
+
8796
+ iter.reset();
8797
+
8798
+ ASSERT_OK(db->DestroyColumnFamilyHandle(cfh));
8799
+ ASSERT_OK(db->Close());
8800
+ ASSERT_OK(DestroyDB(dbname, options_));
8801
+ }
8802
+
8803
+ TEST_P(UserDefinedIndexTest, QueryCrossTwoFiles) {
8804
+ BlockBasedTableOptions table_options;
8805
+ options_.num_levels = 50;
8806
+ options_.compaction_style = kCompactionStyleUniversal;
8807
+ options_.disable_auto_compactions = true;
8808
+ options_.sst_partitioner_factory = NewSstPartitionerFixedPrefixFactory(4);
8809
+ std::string dbname = test::PerThreadDBPath("user_defined_index_test");
8810
+ std::string ingest_file = dbname + "test.sst";
8811
+
8812
+ // Set up the user-defined index factory
8813
+ auto user_defined_index_factory =
8814
+ std::make_shared<TestUserDefinedIndexFactory>();
8815
+ table_options.user_defined_index_factory = user_defined_index_factory;
8816
+
8817
+ // Set up custom flush block policy that flushes every 3 keys
8818
+ table_options.flush_block_policy_factory =
8819
+ std::make_shared<CustomFlushBlockPolicyFactory>();
8820
+
8821
+ options_.table_factory.reset(NewBlockBasedTableFactory(table_options));
8822
+
8823
+ auto create_ingestion_data_file = [&](const std::string& filename,
8824
+ const std::string& value) {
8825
+ std::unique_ptr<SstFileWriter> writer;
8826
+ writer.reset(new SstFileWriter(EnvOptions(), options_));
8827
+ ASSERT_OK(writer->Open(filename));
8828
+ auto kvs = generateKVWithValue(100, value);
8829
+
8830
+ for (const auto& kv : kvs) {
8831
+ ASSERT_OK(writer->Put(kv.first, kv.second));
8832
+ }
8833
+ ASSERT_OK(writer->Finish());
8834
+ writer.reset();
8835
+ };
8836
+
8837
+ // Create first ingestion file with data
8838
+ create_ingestion_data_file(ingest_file + "_0", "old");
8839
+
8840
+ std::unique_ptr<DB> db;
8841
+ options_.create_if_missing = true;
8842
+ Status s = DB::Open(options_, dbname, &db);
8843
+ ASSERT_OK(s);
8844
+ ASSERT_TRUE(db != nullptr);
8845
+ ColumnFamilyHandle* cfh = nullptr;
8846
+ ASSERT_OK(db->CreateColumnFamily(options_, "new_cf", &cfh));
8847
+
8848
+ IngestExternalFileOptions ifo;
8849
+ // ingest data file key00~key99
8850
+ s = db->IngestExternalFile(cfh, {ingest_file + "_0"}, ifo);
8851
+ ASSERT_OK(s);
8852
+
8853
+ // Compact the file with SST partitioner, so that files are split into
8854
+ // multiple ones
8855
+ s = db->CompactRange(
8856
+ {.exclusive_manual_compaction = true,
8857
+ .bottommost_level_compaction = BottommostLevelCompaction::kForce},
8858
+ cfh, nullptr, nullptr);
8859
+ ASSERT_OK(s);
8860
+
8861
+ std::vector<Slice> range = {
8862
+ // Each range span across 2 files
8863
+ Slice("key16"),
8864
+ Slice("key24"),
8865
+ Slice("key26"),
8866
+ Slice("key34"),
8867
+ };
8868
+
8869
+ if (is_reverse_comparator_) {
8870
+ std::reverse(range.begin(), range.end());
8871
+ }
8872
+
8873
+ Slice ub("");
8874
+ ReadOptions ro;
8875
+ ro.iterate_upper_bound = &ub;
8876
+ std::unique_ptr<Iterator> iter(db->NewIterator(ro, cfh));
8877
+ ASSERT_NE(iter, nullptr);
8878
+
8879
+ MultiScanArgs scan_opts(options_.comparator);
8880
+ std::unordered_map<std::string, std::string> property_bag;
8881
+ auto read_key_per_range_limit = 2;
8882
+ property_bag["count"] = std::to_string(read_key_per_range_limit);
8883
+
8884
+ for (size_t i = 0; i < range.size() / 2; i++) {
8885
+ scan_opts.insert(range[i * 2], range[i * 2 + 1],
8886
+ std::optional(property_bag));
8887
+ }
8888
+ iter->Prepare(scan_opts);
8889
+
8890
+ for (size_t i = 0; i < range.size() / 2; i++) {
8891
+ // Update upper bound before each seek
8892
+ ub = range[2 * i + 1];
8893
+ auto key_count = 0;
8894
+ for (iter->Seek(range[i * 2]); iter->Valid(); iter->Next()) {
8895
+ key_count++;
8896
+ ASSERT_EQ(iter->value(), "old");
8897
+ if (key_count >= read_key_per_range_limit) {
8898
+ break;
8899
+ }
8900
+ }
8901
+ ASSERT_OK(iter->status());
8902
+ ASSERT_EQ(key_count, read_key_per_range_limit);
8903
+ }
8904
+
8905
+ // Create another ingestion file with range delete only that covers the first
8906
+ // file to delete all of its keys.
8907
+ {
8908
+ std::unique_ptr<SstFileWriter> writer;
8909
+ writer.reset(new SstFileWriter(EnvOptions(), options_));
8910
+ ASSERT_OK(writer->Open(ingest_file + "_1"));
8911
+ if (is_reverse_comparator_) {
8912
+ ASSERT_OK(writer->DeleteRange("keyz", "key"));
8913
+ } else {
8914
+ ASSERT_OK(writer->DeleteRange("key", "keyz"));
8915
+ }
8916
+ ASSERT_OK(writer->Finish());
8917
+ writer.reset();
8918
+ }
8919
+ s = db->IngestExternalFile(cfh, {ingest_file + "_1"}, ifo);
8920
+ ASSERT_OK(s);
8921
+
8922
+ // ingest new data
8923
+ create_ingestion_data_file(ingest_file + "_2", "new");
8924
+ s = db->IngestExternalFile(cfh, {ingest_file + "_2"}, ifo);
8925
+ ASSERT_OK(s);
8926
+
8927
+ iter.reset(db->NewIterator(ro, cfh));
8928
+ ASSERT_NE(iter, nullptr);
8929
+ ASSERT_OK(iter->status());
8930
+
8931
+ iter->Prepare(scan_opts);
8932
+
8933
+ for (size_t i = 0; i < range.size() / 2; i++) {
8934
+ // Update upper bound before each seek
8935
+ ub = range[2 * i + 1];
8936
+ auto key_count = 0;
8937
+ for (iter->Seek(range[i * 2]); iter->Valid(); iter->Next()) {
8938
+ key_count++;
8939
+ ASSERT_EQ(iter->value(), "new");
8940
+ if (key_count >= read_key_per_range_limit) {
8941
+ break;
8942
+ }
8943
+ }
8944
+ ASSERT_OK(iter->status());
8945
+ ASSERT_EQ(key_count, read_key_per_range_limit);
8946
+ }
8947
+
8948
+ iter.reset();
8949
+
8950
+ ASSERT_OK(db->DestroyColumnFamilyHandle(cfh));
8951
+ ASSERT_OK(db->Close());
8952
+ ASSERT_OK(DestroyDB(dbname, options_));
8953
+ }
8954
+
8955
+ INSTANTIATE_TEST_CASE_P(UserDefinedIndexTest, UserDefinedIndexTest,
8956
+ ::testing::Values(BytewiseComparator(),
8957
+ ReverseBytewiseComparator()));
8958
+
8959
+ struct UserDefinedIndexStressTestParam {
8960
+ const Comparator* comparator;
8961
+ bool enable_udi;
8962
+ bool enable_compaction_with_sst_partitioner;
8963
+
8964
+ using UserDefinedIndexStressTestTuple =
8965
+ std::tuple<const Comparator*, bool, bool>;
8966
+
8967
+ UserDefinedIndexStressTestParam(const UserDefinedIndexStressTestTuple& tuple)
8968
+ : comparator(std::get<0>(tuple)),
8969
+ enable_udi(std::get<1>(tuple)),
8970
+ enable_compaction_with_sst_partitioner(std::get<2>(tuple)) {}
8971
+ };
8972
+
8973
+ std::ostream& operator<<(std::ostream& os,
8974
+ const UserDefinedIndexStressTestParam& param) {
8975
+ return os << "UserDefinedIndexStressTestParam{comparator="
8976
+ << (param.comparator ? param.comparator->Name() : "nullptr")
8977
+ << ", enable_udi=" << param.enable_udi
8978
+ << ", enable_compaction_with_sst_partitioner="
8979
+ << param.enable_compaction_with_sst_partitioner << "}";
8980
+ }
8981
+
8982
+ constexpr auto kVerbose = false;
8983
+
8984
+ struct DataRange {
8985
+ size_t start; // inclusive
8986
+ size_t end; // exclusive
8987
+ std::string value;
8988
+ bool is_range_delete;
8989
+ bool skipped;
8990
+ size_t scan_key_count_limit;
8991
+ std::string start_key;
8992
+ std::string end_key;
8993
+
8994
+ // print the range in human readable format
8995
+ std::string ToString() const {
8996
+ std::ostringstream oss;
8997
+ oss << "[" << start << ", " << end << "), value: " << value
8998
+ << ", is_range_delete: " << is_range_delete << ", skipped: " << skipped
8999
+ << ", scan_key_count_limit: " << scan_key_count_limit
9000
+ << ", start_key: " << start_key << ", end_key: " << end_key;
9001
+ return oss.str();
9002
+ }
9003
+ };
9004
+ class UserDefinedIndexStressTest
9005
+ : public UserDefinedIndexTestBase,
9006
+ public testing::WithParamInterface<
9007
+ UserDefinedIndexStressTestParam::UserDefinedIndexStressTestTuple> {
9008
+ public:
9009
+ void SetUp() override {
9010
+ rand_seed_ = static_cast<uint32_t>(
9011
+ std::chrono::duration_cast<std::chrono::nanoseconds>(
9012
+ std::chrono::system_clock::now().time_since_epoch())
9013
+ .count());
9014
+
9015
+ std::cout << "Random seed: " << rand_seed_ << std::endl;
9016
+
9017
+ rnd = Random(rand_seed_);
9018
+ UserDefinedIndexStressTestParam param = GetParam();
9019
+ comparator_ = param.comparator;
9020
+ enable_udi_ = param.enable_udi;
9021
+ enable_compaction_with_sst_partitioner_ =
9022
+ param.enable_compaction_with_sst_partitioner;
9023
+ options_.comparator = comparator_;
9024
+ is_reverse_comparator_ = comparator_ == ReverseBytewiseComparator();
9025
+ options_.compaction_style = kCompactionStyleUniversal;
9026
+
9027
+ // Set up custom flush block policy that flushes every 3 keys
9028
+ table_options_.flush_block_policy_factory =
9029
+ std::make_shared<CustomFlushBlockPolicyFactory>();
9030
+
9031
+ options_.table_factory.reset(NewBlockBasedTableFactory(table_options_));
9032
+ }
9033
+
9034
+ void TearDown() override {
9035
+ ASSERT_OK(db_->DestroyColumnFamilyHandle(ingest_cfh_));
9036
+ ASSERT_OK(db_->DestroyColumnFamilyHandle(regular_cfh_));
9037
+
9038
+ ASSERT_OK(db_->Close());
9039
+ ASSERT_OK(DestroyDB(dbname_, options_));
9040
+ }
9041
+
9042
+ protected:
9043
+ static constexpr auto kKeyRange = 100;
9044
+ bool enable_udi_{};
9045
+ bool enable_compaction_with_sst_partitioner_{};
9046
+ uint32_t rand_seed_{};
9047
+ std::shared_ptr<UserDefinedIndexFactory> user_defined_index_factory_;
9048
+ BlockBasedTableOptions table_options_;
9049
+ const Comparator* comparator_{};
9050
+ bool is_reverse_comparator_{};
9051
+ Random rnd{0};
9052
+ ColumnFamilyHandle* ingest_cfh_ = nullptr;
9053
+ ColumnFamilyHandle* regular_cfh_ = nullptr;
9054
+ std::unique_ptr<DB> db_;
9055
+ std::vector<std::vector<DataRange>> ranges_in_levels_;
9056
+ std::string dbname_;
9057
+
9058
+ void SetupDB(const std::string& dbname) {
9059
+ options_.create_if_missing = true;
9060
+ options_.disable_auto_compactions = true;
9061
+ Status s = DB::Open(options_, dbname, &db_);
9062
+ ASSERT_OK(s);
9063
+ ASSERT_TRUE(db_ != nullptr);
9064
+ if (enable_compaction_with_sst_partitioner_) {
9065
+ // Use a SST partitioner to create multiple files, use the first 4 bytes
9066
+ // of key to partition the file, The key is formatted with 2 digit
9067
+ // following "key" string, e.g. key01, key99
9068
+ options_.sst_partitioner_factory = NewSstPartitionerFixedPrefixFactory(4);
9069
+ }
9070
+
9071
+ ASSERT_OK(db_->CreateColumnFamily(options_, "regular_cf", &regular_cfh_));
9072
+
9073
+ if (enable_udi_) {
9074
+ // Set up the user-defined index factory
9075
+ user_defined_index_factory_ =
9076
+ std::make_shared<TestUserDefinedIndexFactory>();
9077
+ table_options_.user_defined_index_factory = user_defined_index_factory_;
9078
+ }
9079
+
9080
+ options_.table_factory.reset(NewBlockBasedTableFactory(table_options_));
9081
+ ASSERT_OK(db_->CreateColumnFamily(options_, "ingest_cf", &ingest_cfh_));
9082
+ }
9083
+
9084
+ template <typename T>
9085
+ std::string FormatKey(T i) {
9086
+ std::stringstream ss;
9087
+ ss << std::setw(2) << std::setfill('0') << i;
9088
+ return "key" + ss.str();
9089
+ }
9090
+
9091
+ std::vector<DataRange> GenerateKeyRanges(size_t range_count,
9092
+ int skip_range_count,
9093
+ const std::string& value) {
9094
+ std::set<size_t> boundaries;
9095
+ // generate n + 1 number of unique boundaries to form n contiguoes ranges
9096
+ while (boundaries.size() < range_count + 1) {
9097
+ boundaries.insert(rnd.Uniform(kKeyRange));
9098
+ }
9099
+ std::vector<size_t> sorted_boundaries(boundaries.begin(), boundaries.end());
9100
+ if (is_reverse_comparator_) {
9101
+ std::reverse(sorted_boundaries.begin(), sorted_boundaries.end());
9102
+ }
9103
+ auto ranges = std::vector<DataRange>();
9104
+ std::optional<size_t> prev_bound;
9105
+ for (auto it = sorted_boundaries.begin(); it != sorted_boundaries.end();
9106
+ it++) {
9107
+ if (prev_bound.has_value()) {
9108
+ ranges.push_back({.start = prev_bound.value(),
9109
+ .end = *it,
9110
+ .value = value,
9111
+ .is_range_delete = rnd.OneIn(6),
9112
+ .skipped = false,
9113
+ .scan_key_count_limit = rnd.Uniform(10) + 1,
9114
+ .start_key = FormatKey(prev_bound.value()),
9115
+ .end_key = FormatKey(*it)});
9116
+ }
9117
+ prev_bound = *it;
9118
+ }
9119
+ // skipped some of them
9120
+ for (int j = 0; j < skip_range_count; j++) {
9121
+ ranges[rnd.Uniform(static_cast<uint32_t>(range_count))].skipped = true;
9122
+ }
9123
+
9124
+ if (kVerbose) {
9125
+ for (auto const& range : ranges) {
9126
+ std::cout << range.ToString() << std::endl;
9127
+ }
9128
+ }
9129
+
9130
+ return ranges;
9131
+ }
9132
+
9133
+ void CreateSstFileWithRanges(const std::string& ingest_file,
9134
+ const DataRange& range) {
9135
+ std::unique_ptr<SstFileWriter> writer =
9136
+ std::make_unique<SstFileWriter>(EnvOptions(), options_);
9137
+ ASSERT_OK(writer->Open(ingest_file));
9138
+
9139
+ assert(range.start != range.end);
9140
+
9141
+ if (range.is_range_delete) {
9142
+ ASSERT_OK(writer->DeleteRange(range.start_key, range.end_key));
9143
+ } else {
9144
+ for (size_t i = range.start; i != range.end;) {
9145
+ auto key = FormatKey(i);
9146
+ range.start < range.end ? i++ : i--;
9147
+ ASSERT_OK(writer->Put(key, range.value));
9148
+ }
9149
+ }
9150
+ ASSERT_OK(writer->Finish()) << range.ToString();
9151
+ }
9152
+
9153
+ void RangeScan(std::unique_ptr<Iterator>& iter,
9154
+ const std::vector<DataRange>& ranges, Slice& upper_bound,
9155
+ std::vector<std::pair<std::string, std::string>>& result,
9156
+ bool use_multi_scan) {
9157
+ ASSERT_NE(iter, nullptr);
9158
+ ASSERT_OK(iter->status());
9159
+ ASSERT_TRUE(!ranges.empty());
9160
+
9161
+ MultiScanArgs scan_opts(options_.comparator);
9162
+ std::unordered_map<std::string, std::string> property_bag;
9163
+ if (use_multi_scan) {
9164
+ for (auto const& range : ranges) {
9165
+ if (range.skipped) {
9166
+ continue;
9167
+ }
9168
+ property_bag["count"] = std::to_string(range.scan_key_count_limit);
9169
+ scan_opts.insert(range.start_key, range.end_key, property_bag);
9170
+ // print range start end key
9171
+ if (kVerbose) {
9172
+ std::cout << "range start " << range.start_key << " end "
9173
+ << range.end_key << std::endl;
9174
+ }
9175
+ }
9176
+ iter->Prepare(scan_opts);
9177
+ ASSERT_OK(iter->status());
9178
+ }
9179
+
9180
+ for (auto const& range : ranges) {
9181
+ if (range.skipped) {
9182
+ continue;
9183
+ }
9184
+ size_t scan_key_count = 0;
9185
+ if (kVerbose) {
9186
+ std::cout << "seek key " << range.start_key << std::endl;
9187
+ }
9188
+ upper_bound = range.end_key;
9189
+ for (iter->Seek(range.start_key);
9190
+ iter->Valid() && scan_key_count < range.scan_key_count_limit;
9191
+ iter->Next()) {
9192
+ if (kVerbose) {
9193
+ std::cout << "key " << iter->key().ToString() << " value "
9194
+ << iter->value().ToString() << std::endl;
9195
+ }
9196
+ result.emplace_back(iter->key().ToString(), iter->value().ToString());
9197
+ scan_key_count++;
9198
+ }
9199
+ ASSERT_OK(iter->status());
9200
+ }
9201
+ }
9202
+
9203
+ void AddDataToRegularCF() {
9204
+ for (auto const& ranges_in_level : ranges_in_levels_) {
9205
+ for (auto const& range : ranges_in_level) {
9206
+ if (!range.skipped) {
9207
+ for (auto i = range.start; i != range.end;
9208
+ range.start < range.end ? i++ : i--) {
9209
+ if (range.is_range_delete) {
9210
+ ASSERT_OK(
9211
+ db_->Delete(WriteOptions(), regular_cfh_, FormatKey(i)));
9212
+ } else {
9213
+ ASSERT_OK(db_->Put(WriteOptions(), regular_cfh_, FormatKey(i),
9214
+ range.value));
9215
+ }
9216
+ }
9217
+ }
9218
+ }
9219
+ }
9220
+ ASSERT_OK(db_->Flush(FlushOptions(), regular_cfh_));
9221
+ }
9222
+
9223
+ void ValidateQueryResult() {
9224
+ // Query both CF with same range scan and validate result are same
9225
+ for (auto i = 0; i < 200; i++) {
9226
+ if (kVerbose) {
9227
+ std::cout << "iteration " << i << std::endl;
9228
+ }
9229
+ // randomly generate 1 to 3 ranges
9230
+ auto ranges = GenerateKeyRanges(rnd.Uniform(3) + 4, 2, "");
9231
+
9232
+ // Query regular CF
9233
+ std::vector<std::pair<std::string, std::string>> expected_result;
9234
+ Slice upper_bound("");
9235
+ ReadOptions ro;
9236
+ ro.iterate_upper_bound = &upper_bound;
9237
+
9238
+ std::unique_ptr<Iterator> iter(db_->NewIterator(ro, regular_cfh_));
9239
+ ASSERT_NO_FATAL_FAILURE(
9240
+ RangeScan(iter, ranges, upper_bound, expected_result, false));
9241
+ ASSERT_OK(iter->status());
9242
+
9243
+ // Query ingest CF
9244
+ iter.reset(db_->NewIterator(ro, ingest_cfh_));
9245
+ std::vector<std::pair<std::string, std::string>> ingest_cf_result;
9246
+ ASSERT_NO_FATAL_FAILURE(
9247
+ RangeScan(iter, ranges, upper_bound, ingest_cf_result, false));
9248
+
9249
+ ASSERT_EQ(expected_result, ingest_cf_result);
9250
+ ASSERT_OK(iter->status());
9251
+
9252
+ // Query ingest CF with UDI if it is enabled
9253
+ if (enable_udi_) {
9254
+ ro.table_index_factory = user_defined_index_factory_.get();
9255
+ }
9256
+
9257
+ iter.reset(db_->NewIterator(ro, ingest_cfh_));
9258
+ std::vector<std::pair<std::string, std::string>>
9259
+ ingest_cf_multi_scan_result;
9260
+ ASSERT_NO_FATAL_FAILURE(RangeScan(iter, ranges, upper_bound,
9261
+ ingest_cf_multi_scan_result, true));
9262
+ ASSERT_EQ(expected_result, ingest_cf_multi_scan_result);
9263
+ ASSERT_OK(iter->status());
9264
+ }
9265
+ }
9266
+
9267
+ void IngestFilesInOneLevel(const std::vector<DataRange>& ranges_in_level,
9268
+ const std::string& ingest_file_name_prefix,
9269
+ size_t& ingest_file_count,
9270
+ const IngestExternalFileOptions& ifo) {
9271
+ std::vector<std::string> ingest_files;
9272
+ // Generate SST file and bulk load them one level at a time
9273
+ for (auto const& range : ranges_in_level) {
9274
+ if (!range.skipped) {
9275
+ ASSERT_NO_FATAL_FAILURE(CreateSstFileWithRanges(
9276
+ ingest_file_name_prefix + std::to_string(ingest_file_count),
9277
+ range));
9278
+ ingest_files.push_back(ingest_file_name_prefix +
9279
+ std::to_string(ingest_file_count));
9280
+ ingest_file_count++;
9281
+ }
9282
+ }
9283
+
9284
+ ASSERT_OK(db_->IngestExternalFile(ingest_cfh_, ingest_files, ifo));
9285
+ }
9286
+
9287
+ void IngestDataToCF() {
9288
+ IngestExternalFileOptions ifo;
9289
+ ifo.snapshot_consistency = false;
9290
+ auto ingest_file_name_prefix = dbname_ + "ingest_file_";
9291
+ size_t ingest_file_count = 0;
9292
+ for (auto const& ranges_in_level : ranges_in_levels_) {
9293
+ ASSERT_NO_FATAL_FAILURE(IngestFilesInOneLevel(
9294
+ ranges_in_level, ingest_file_name_prefix, ingest_file_count, ifo));
9295
+ }
9296
+
9297
+ ASSERT_GE(ingest_file_count, 0);
9298
+ }
9299
+
9300
+ void CompactIngestedCF() {
9301
+ auto s = db_->CompactRange(
9302
+ {.exclusive_manual_compaction = true,
9303
+ .bottommost_level_compaction = BottommostLevelCompaction::kForce},
9304
+ ingest_cfh_, nullptr, nullptr);
9305
+ ASSERT_OK(s);
9306
+ }
9307
+ };
9308
+
9309
+ // TODO(xingbo)
9310
+ // This test is disabled due to following test case condition:
9311
+ // level n: delete range 4-6
9312
+ // level n+1: data range 0-------10
9313
+ // query: 3-9, count=2.
9314
+ // Becuase query count == 2, level n+1 would only prepare 3-5. but since 4-6
9315
+ // got deleted in the upper level, they are not returned, so only 3 is
9316
+ // returned. Meantime the query should have return [3, 6]
9317
+ // One way to fix this is by preparing more data blocks once prepared blocks are
9318
+ // exhausted, but upper bound is not reached yet.
9319
+ // This requires following changes:
9320
+ // 1. Fix out of bound flag in block table iterator. Only set it if the key is
9321
+ // larger than the upper bound.
9322
+ // 2. Refactor the prepared block single dimension vector into 2 dimension of
9323
+ // vectors, so that more blocks could be prepared if needed.
9324
+ TEST_P(UserDefinedIndexStressTest, DISABLED_PartialDeleteRange) {
9325
+ // Create 2 column families. One use normal put/del, the other uses sst
9326
+ // ingest Randomly generate multiple non overlapping range for multiple
9327
+ // levels Range scan same range between the 2 CF and validate the result is
9328
+ // same
9329
+ SCOPED_TRACE("Start with random seed: " + std::to_string(rand_seed_));
9330
+ dbname_ =
9331
+ test::PerThreadDBPath("UserDefinedIndexStressTest_PartialDeleteRange");
9332
+ SCOPED_TRACE("dbname: " + dbname_);
9333
+ ASSERT_NO_FATAL_FAILURE(SetupDB(dbname_));
9334
+
9335
+ for (int i = 0; i < 5; i++) {
9336
+ ranges_in_levels_.push_back(
9337
+ GenerateKeyRanges(rnd.Uniform(3) + 4, 2,
9338
+ "L" + std::to_string(options_.num_levels - 1 - i)));
9339
+ }
9340
+
9341
+ ASSERT_NO_FATAL_FAILURE(IngestDataToCF());
9342
+
9343
+ if (enable_compaction_with_sst_partitioner_) {
9344
+ ASSERT_NO_FATAL_FAILURE(CompactIngestedCF());
9345
+ }
9346
+
9347
+ ASSERT_NO_FATAL_FAILURE(AddDataToRegularCF());
9348
+
9349
+ ASSERT_NO_FATAL_FAILURE(ValidateQueryResult());
9350
+ }
9351
+
9352
+ TEST_P(UserDefinedIndexStressTest, DeleteRange) {
9353
+ // Create 2 column families. One use normal put/del, the other uses sst
9354
+ // ingest.
9355
+ // Test the case where there are 3 levels, the middle level is a delete range
9356
+ // file that span across the entire key space.
9357
+ // Range scan same range between the 2 CF and validate the result is same
9358
+ SCOPED_TRACE("Start with random seed: " + std::to_string(rand_seed_));
9359
+ dbname_ = test::PerThreadDBPath("UserDefinedIndexStressTest_DeleteRange");
9360
+ SCOPED_TRACE("dbname: " + dbname_);
9361
+ ASSERT_NO_FATAL_FAILURE(SetupDB(dbname_));
9362
+
9363
+ // Test 3 levels.
9364
+ // bottom level is normal data files.
9365
+ ranges_in_levels_.push_back(GenerateKeyRanges(rnd.Uniform(3) + 4, 2, "L6"));
9366
+ // middle level delete range between each level
9367
+ if (is_reverse_comparator_) {
9368
+ ranges_in_levels_.push_back({{.start = 100,
9369
+ .end = 0,
9370
+ .is_range_delete = true,
9371
+ .skipped = false,
9372
+ .start_key = "keyz",
9373
+ .end_key = "key"}});
9374
+ } else {
9375
+ ranges_in_levels_.push_back({{.start = 0,
9376
+ .end = 100,
9377
+ .is_range_delete = true,
9378
+ .skipped = false,
9379
+ .start_key = "key",
9380
+ .end_key = "keyz"}});
9381
+ }
9382
+ // Top level is normal data files
9383
+ ranges_in_levels_.push_back(GenerateKeyRanges(rnd.Uniform(3) + 4, 2, "L4"));
9384
+
9385
+ IngestExternalFileOptions ifo;
9386
+ ifo.snapshot_consistency = false;
9387
+ auto ingest_file_name_prefix = dbname_ + "ingest_file_";
9388
+ size_t ingest_file_count = 0;
9389
+ auto first_level = true;
9390
+ for (auto const& ranges_in_level : ranges_in_levels_) {
9391
+ ASSERT_NO_FATAL_FAILURE(IngestFilesInOneLevel(
9392
+ ranges_in_level, ingest_file_name_prefix, ingest_file_count, ifo));
9393
+ if (first_level) {
9394
+ first_level = false;
9395
+ if (enable_compaction_with_sst_partitioner_) {
9396
+ // When compaction is enabled, do a compaction at the first level
9397
+ ASSERT_NO_FATAL_FAILURE(CompactIngestedCF());
9398
+ }
9399
+ }
9400
+ }
9401
+
9402
+ ASSERT_NO_FATAL_FAILURE(AddDataToRegularCF());
9403
+
9404
+ ASSERT_NO_FATAL_FAILURE(ValidateQueryResult());
9405
+ }
9406
+
9407
+ INSTANTIATE_TEST_CASE_P(
9408
+ UserDefinedIndexStressTest, UserDefinedIndexStressTest,
9409
+ testing::Combine(testing::Values(BytewiseComparator(),
9410
+ ReverseBytewiseComparator()),
9411
+ testing::Bool(), testing::Bool()));
8027
9412
  } // namespace ROCKSDB_NAMESPACE
8028
9413
 
8029
9414
  int main(int argc, char** argv) {